diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 2023ea2a..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index c5217a32..48b1d15d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,8 @@ hs_err_pid* out # iml -*.iml \ No newline at end of file +*.iml + +.DS_Store +.aider* +.env diff --git "a/Basics/Java\351\235\242\350\257\225\345\237\272\347\241\200\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Basics/Java\351\235\242\350\257\225\345\237\272\347\241\200\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 112f0e99..00000000 --- "a/Basics/Java\351\235\242\350\257\225\345\237\272\347\241\200\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,67 +0,0 @@ -- [简述线程、程序、进程的基本概念。以及他们之间关系是什么?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E7%AE%80%E8%BF%B0%E7%BA%BF%E7%A8%8B%E7%A8%8B%E5%BA%8F%E8%BF%9B%E7%A8%8B%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E4%BB%A5%E5%8F%8A%E4%BB%96%E4%BB%AC%E4%B9%8B%E9%97%B4%E5%85%B3%E7%B3%BB%E6%98%AF%E4%BB%80%E4%B9%88) -- [Java 语言有哪些特点?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#java-%E8%AF%AD%E8%A8%80%E6%9C%89%E5%93%AA%E4%BA%9B%E7%89%B9%E7%82%B9) -- [面向对象和面向过程的区别](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%92%8C%E9%9D%A2%E5%90%91%E8%BF%87%E7%A8%8B%E7%9A%84%E5%8C%BA%E5%88%AB) -- **==、hashcode和equals** - - [==与equals](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#-%E4%B8%8E-equals%E9%87%8D%E8%A6%81) - - [hashcode和equlas](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#hashcode-%E4%B8%8E-equals-%E9%87%8D%E8%A6%81) - - [hashcode和equlas代码小例子](/Basics/src/com/equal/Student.java) -- **关于 JVM JDK 和 JRE 最详细通俗的解答** - - [JVM](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#jvm) - - [JDK 和 JRE](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#jdk-%E5%92%8C-jre) -- [Java和C++的区别?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#java%E5%92%8Cc%E7%9A%84%E5%8C%BA%E5%88%AB) -- **基本类型** - - [自动装箱与拆箱](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E8%87%AA%E5%8A%A8%E8%A3%85%E7%AE%B1%E4%B8%8E%E6%8B%86%E7%AE%B1) - - [字符型常量和字符串常量的区别?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%AD%97%E7%AC%A6%E5%9E%8B%E5%B8%B8%E9%87%8F%E5%92%8C%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%B8%B8%E9%87%8F%E7%9A%84%E5%8C%BA%E5%88%AB) - - [说说&和&&的区别](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E8%AF%B4%E8%AF%B4%E5%92%8C%E7%9A%84%E5%8C%BA%E5%88%AB) - - [short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1; 有什么错?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#short-s1--1-s1--s1--1%E6%9C%89%E4%BB%80%E4%B9%88%E9%94%99-short-s1--1-s1--1-%E6%9C%89%E4%BB%80%E4%B9%88%E9%94%99) [demo](/Basics/src/com/type/TypeConvert.java) - - [char 型变量中能不能存贮一个中文汉字?为什么?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#char-%E5%9E%8B%E5%8F%98%E9%87%8F%E4%B8%AD%E8%83%BD%E4%B8%8D%E8%83%BD%E5%AD%98%E8%B4%AE%E4%B8%80%E4%B8%AA%E4%B8%AD%E6%96%87%E6%B1%89%E5%AD%97%E4%B8%BA%E4%BB%80%E4%B9%88) - - [整形包装缓存池例子vlaueof](/Basics/src/com/pack/IntegerPackDemo.java) -- **构造器** - - [构造器 Constructor 是否可被 override?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E6%9E%84%E9%80%A0%E5%99%A8-constructor-%E6%98%AF%E5%90%A6%E5%8F%AF%E8%A2%AB-override) - - [构造方法有哪些特性?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%E6%9C%89%E5%93%AA%E4%BA%9B%E7%89%B9%E6%80%A7) -- **String** - - [String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#string-stringbuffer-%E5%92%8C-stringbuilder-%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88-string-%E4%B8%BA%E4%BB%80%E4%B9%88%E6%98%AF%E4%B8%8D%E5%8F%AF%E5%8F%98%E7%9A%84) - - [String StringBuffer 和 StringBuilder的代码例子](/Basics/src/com/strings/SbDemo.java) - - [String A = "123"; String B = new String("123");生成几个对象?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#string-a--123-string-b--new-string123%E7%94%9F%E6%88%90%E5%87%A0%E4%B8%AA%E5%AF%B9%E8%B1%A1) - - [String.intern与缓存池](/Basics/src/com/strings/SIntern.java) -- **对象** - - [Java 面向对象编程三大特性: 封装 继承 多态](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E9%87%8D%E8%BD%BD%E5%92%8C%E9%87%8D%E5%86%99%E7%9A%84%E5%8C%BA%E5%88%AB) - - [接口和抽象类的区别是什么?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E6%8E%A5%E5%8F%A3%E5%92%8C%E6%8A%BD%E8%B1%A1%E7%B1%BB%E7%9A%84%E5%8C%BA%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88) - - [成员变量与局部变量的区别有哪些?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E6%88%90%E5%91%98%E5%8F%98%E9%87%8F%E4%B8%8E%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E7%9A%84%E5%8C%BA%E5%88%AB%E6%9C%89%E5%93%AA%E4%BA%9B) - - [重载和重写的区别](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E9%87%8D%E8%BD%BD%E5%92%8C%E9%87%8D%E5%86%99%E7%9A%84%E5%8C%BA%E5%88%AB) - - [创建一个对象用什么运算符?对象实体与对象引用有何不同?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%94%A8%E4%BB%80%E4%B9%88%E8%BF%90%E7%AE%97%E7%AC%A6%E5%AF%B9%E8%B1%A1%E5%AE%9E%E4%BD%93%E4%B8%8E%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%E6%9C%89%E4%BD%95%E4%B8%8D%E5%90%8C) - - [对象的相等与指向他们的引用相等,两者有什么不同?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%9B%B8%E7%AD%89%E4%B8%8E%E6%8C%87%E5%90%91%E4%BB%96%E4%BB%AC%E7%9A%84%E5%BC%95%E7%94%A8%E7%9B%B8%E7%AD%89%E4%B8%A4%E8%80%85%E6%9C%89%E4%BB%80%E4%B9%88%E4%B8%8D%E5%90%8C) -- [为什么Java中只有值传递?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E4%B8%BA%E4%BB%80%E4%B9%88java%E4%B8%AD%E5%8F%AA%E6%9C%89%E5%80%BC%E4%BC%A0%E9%80%92) - - [参考这篇文章](https://github.com/Snailclimb/JavaGuide/blob/master/docs/essential-content-for-interview/PreparingForInterview/%E5%BA%94%E5%B1%8A%E7%94%9F%E9%9D%A2%E8%AF%95%E6%9C%80%E7%88%B1%E9%97%AE%E7%9A%84%E5%87%A0%E9%81%93Java%E5%9F%BA%E7%A1%80%E9%97%AE%E9%A2%98.md#%E4%B8%80-%E4%B8%BA%E4%BB%80%E4%B9%88-java-%E4%B8%AD%E5%8F%AA%E6%9C%89%E5%80%BC%E4%BC%A0%E9%80%92) - - [基本类型传递代码例子](/Basics/src/com/transfer/TransferDemo.java) - - [数组类型传递代码例子](/Basics/src/com/transfer/TransferDemo2.java) - - [对象引用类型传递代码例子](/Basics/src/com/transfer/TransferDemo3.java) -- **关键字** - - [关于 final 关键字的一些总结](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%85%B3%E4%BA%8E-final-%E5%85%B3%E9%94%AE%E5%AD%97%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93) **一般关键字的面试,答的时候按照变量、方法和类去总结** - - [static 关键字](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#static-%E5%85%B3%E9%94%AE%E5%AD%97) - - [this 关键字](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#this-%E5%85%B3%E9%94%AE%E5%AD%97) - - [super 关键字](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#super-%E5%85%B3%E9%94%AE%E5%AD%97) - - [final, finally, finalize 的区别](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#final-finally-finalize-%E7%9A%84%E5%8C%BA%E5%88%AB) - - [请说出作用域 public,private,protected,以及不写时的区别](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E8%AF%B7%E8%AF%B4%E5%87%BA%E4%BD%9C%E7%94%A8%E5%9F%9F-publicprivateprotected%E4%BB%A5%E5%8F%8A%E4%B8%8D%E5%86%99%E6%97%B6-%E7%9A%84%E5%8C%BA%E5%88%AB) -- **异常** - - [Java 中的异常处理](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#java-%E4%B8%AD%E7%9A%84%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86) - - [请写出你最常见到的 5 个 runtime exception](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E8%AF%B7%E5%86%99%E5%87%BA%E4%BD%A0%E6%9C%80%E5%B8%B8%E8%A7%81%E5%88%B0%E7%9A%84-5-%E4%B8%AA-runtime-exception) -- **IO** - - [获取用键盘输入常用的两种方法](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E8%8E%B7%E5%8F%96%E7%94%A8%E9%94%AE%E7%9B%98%E8%BE%93%E5%85%A5%E5%B8%B8%E7%94%A8%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95) - - [Java 中 IO 流分为几种?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#java-%E4%B8%AD-io-%E6%B5%81%E5%88%86%E4%B8%BA%E5%87%A0%E7%A7%8D) - - [既然有了字节流,为什么还要有字符流?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E6%97%A2%E7%84%B6%E6%9C%89%E4%BA%86%E5%AD%97%E8%8A%82%E6%B5%81%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%98%E8%A6%81%E6%9C%89%E5%AD%97%E7%AC%A6%E6%B5%81) - - [BIO,NIO,AIO 有什么区别?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#bionioaio-%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB) -- **反射** - - [反射是什么?](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6%E4%BB%8B%E7%BB%8D) - - [静态编译和动态编译](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91%E5%92%8C%E5%8A%A8%E6%80%81%E7%BC%96%E8%AF%91) - - [反射机制优缺点](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6%E4%BC%98%E7%BC%BA%E7%82%B9) - - [反射的应用场景](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E5%8F%8D%E5%B0%84%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF) - - [反射得到Class对象的三种方式代码例子](/Basics/src/com/reflect/ReflectDemo.java) - - [反射访问并调用构造方法的代码例子](/Basics/src/com/reflect/ConstructorsDemo.java) - - [反射访问并调用成员变量的代码例子](/Basics/src/com/reflect/FieldDemo.java) - - [反射访问并调用成员方法的代码例子](/Basics/src/com/reflect/MethodDemo.java) -- **拷贝** - - [深拷贝 vs 浅拷贝](https://github.com/DreamCats/JavaBooks/blob/master/Basics/Java%E9%9D%A2%E8%AF%95%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#%E6%B7%B1%E6%8B%B7%E8%B4%9D-vs-%E6%B5%85%E6%8B%B7%E8%B4%9D) - - [深浅拷贝参考](https://juejin.im/post/5c988a7ef265da6116246d11) - - [浅拷贝代码例子](/Basics/src/com/copy/ShallowCopyDemo.java) - - [深拷贝代码例子](/Basics/src/com/copy/Student.java) diff --git "a/Basics/Java\351\235\242\350\257\225\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Basics/Java\351\235\242\350\257\225\345\237\272\347\241\200\347\237\245\350\257\206.md" deleted file mode 100644 index be380cfb..00000000 --- "a/Basics/Java\351\235\242\350\257\225\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ /dev/null @@ -1,555 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## 面向对象和面向过程的区别 - -- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。** -- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。 - -> 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。 -> -> 而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。 - -## Java 语言有哪些特点? - -1. 简单易学; -2. 面向对象(封装,继承,多态); -3. 平台无关性( Java 虚拟机实现平台无关性); -4. 可靠性; -5. 安全性; -6. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持); -7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); -8. 编译与解释并存; - -## 关于 JVM JDK 和 JRE 最详细通俗的解答 - -### JVM - -Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。 - -**什么是字节码?采用字节码的好处是什么?** - -> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同操作系统的计算机上运行。 - -**Java 程序从源代码到运行一般有下面3步:** - -![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png) - -我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。 - -> HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。 - -**总结:** - -Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 - -### JDK 和 JRE - -JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。 - -JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。 - -如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。 - -## Java和C++的区别? - -我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来! - -- 都是面向对象的语言,都支持封装、继承和多态 -- Java 不提供指针来直接访问内存,程序内存更加安全 -- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。 -- Java 有自动内存管理机制,不需要程序员手动释放无用内存 - -## 什么是 Java 程序的主类 应用程序和小程序的主类有何不同? - -一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。 - -## Java 应用程序与小程序之间有哪些差别? - -简单说应用程序是从主线程启动(也就是 `main()` 方法)。applet 小程序没有 `main()` 方法,主要是嵌在浏览器页面上运行(调用`init()`或者`run()`来启动),嵌入浏览器这点跟 flash 的小游戏类似。 - -## 字符型常量和字符串常量的区别? - -1. 形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符 -2. 含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置) -3. 占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) - -![参考-JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) - -## 构造器 Constructor 是否可被 override? - -在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。 - -## 重载和重写的区别 - -### 重载 - -发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 - -### 重写 - - 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。**也就是说方法提供的行为改变,而方法的外貌并没有改变。** - -## Java 面向对象编程三大特性: 封装 继承 多态 - -### 封装 - -封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 - -### 继承 - -继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。 - -**关于继承如下 3 点请记住:** - -1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。 -2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 -3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 - -### 多态 - -所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 - -在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 - -## String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? - -**可变性** - -简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 - -**线程安全性** - -String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。  - -**性能** - -每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 - -**对于三者使用的总结:** - -1. 操作少量的数据: 适用String -2. 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder -3. 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer - -## 自动装箱与拆箱 - -- **装箱**:将基本类型用它们对应的引用类型包装起来; -- **拆箱**:将包装类型转换为基本数据类型; - -## 在一个静态方法内调用一个非静态成员为什么是非法的? - -由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 - -## 在 Java 中定义一个不做事且没有参数的构造方法的作用 - -Java 程序在执行子类的构造方法之前,如果没有用 `super() `来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 `super() `来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 - -## import java和javax有什么区别? - -刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 - -## 接口和抽象类的区别是什么? - -1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。 -2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。 -3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。 -4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 -5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。 - -备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。 - -## 成员变量与局部变量的区别有哪些? - -1. 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。 -2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 -3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 -4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。 - -## 创建一个对象用什么运算符?对象实体与对象引用有何不同? - -new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。 - -## 什么是方法的返回值?返回值在类的方法里的作用是什么? - -方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! - -## 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么? - -主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 - -## 构造方法有哪些特性? - -1. 名字与类名相同。 -2. 没有返回值,但不能用void声明构造函数。 -3. 生成类的对象时自动执行,无需调用。 - -## 静态方法和实例方法有何不同 - -1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 -2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。 - -## 对象的相等与指向他们的引用相等,两者有什么不同? - -对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。 - -## 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? - -帮助子类做初始化工作。 - -## == 与 equals(重要) - -**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。 - -**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: - -- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。 -- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 - -**举个例子:** - -```java -public class test1 { - public static void main(String[] args) { - String a = new String("ab"); // a 为一个引用 - String b = new String("ab"); // b为另一个引用,对象的内容一样 - String aa = "ab"; // 放在常量池中 - String bb = "ab"; // 从常量池中查找 - if (aa == bb) // true - System.out.println("aa==bb"); - if (a == b) // false,非同一对象 - System.out.println("a==b"); - if (a.equals(b)) // true - System.out.println("aEQb"); - if (42 == 42.0) { // true - System.out.println("true"); - } - } -} -``` - -**说明:** - -- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。 -- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 - -## hashCode 与 equals (重要) - -面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” - -### hashCode()介绍 - -hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 - -散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) - -### 为什么要有 hashCode - -**我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()`方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - -通过我们可以看出:`hashCode()` 的作用就是**获取哈希码**,也称为散列码;它实际上是返回一个int整数。这个**哈希码的作用**是确定该对象在哈希表中的索引位置。**`hashCode() `在散列表中才有用,在其它情况下没用**。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。 - -### hashCode()与equals()的相关规定 - -1. 如果两个对象相等,则hashcode一定也是相同的 -2. 两个对象相等,对两个对象分别调用equals方法都返回true -3. 两个对象有相同的hashcode值,它们也不一定是相等的 -4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖** -5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) - -## 为什么Java中只有值传递? - -- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。 -- 一个方法可以改变一个对象参数的状态。 -- 一个方法不能让对象参数引用一个新的对象。 - -## 简述线程、程序、进程的基本概念。以及他们之间关系是什么? - -**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 - -**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。 - -**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 -线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。 - -## 关于 final 关键字的一些总结 - -final关键字主要用在三个地方:变量、方法、类。 - -1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 -2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 -3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 - -**Final修饰有啥好处** - -- final的关键字提高了性能,JVM和java应用会缓存final变量; -- final变量可以在多线程环境下保持线程安全; -- 使用final的关键字提高了性能,JVM会对方法变量类进行优化; - -## Java 中的异常处理 - -在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 - -**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 - -这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。 - -**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以0时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。 - -**注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。** - -### Throwable类常用方法 - -- **public string getMessage()**:返回异常发生时的简要描述 -- **public string toString()**:返回异常发生时的详细信息 -- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 -- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 - -### 异常处理总结 - -- **try 块:** 用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 -- **catch 块:** 用于处理try捕获到的异常。 -- **finally 块:** 无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return - 语句时,finally语句块将在方法返回之前被执行。 - -**在以下4种特殊情况下,finally块不会被执行:** - -1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行 -2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行 -3. 程序所在的线程死亡。 -4. 关闭CPU。 - -**注意:** 当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。如下: - -```java - public static int f(int value) { - try { - return value * value; - } finally { - if (value == 2) { - return 0; - } - } - } -``` - -如果调用 `f(2)`,返回值将是0,因为finally语句的返回值覆盖了try语句块的返回值。 - -## Java序列化中如果有些字段不想进行序列化,怎么办? - -对于不想进行序列化的变量,使用transient关键字修饰。 - -transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。 - -## 获取用键盘输入常用的两种方法 - -方法1:通过 Scanner - -```java -Scanner input = new Scanner(System.in); -String s = input.nextLine(); -input.close(); -``` - -方法2:通过 BufferedReader - -```java -BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); -String s = input.readLine(); -``` - -## Java 中 IO 流 - -### Java 中 IO 流分为几种? - - - 按照流的流向分,可以分为输入流和输出流; - - 按照操作单元划分,可以划分为字节流和字符流; - - 按照流的角色划分为节点流和处理流。 - -Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。 - - - InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 - - OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。 - -### 既然有了字节流,为什么还要有字符流? - -问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?** - -回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。 - -### BIO,NIO,AIO 有什么区别? - -- **BIO (Blocking I/O):** 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 -- **NIO (New I/O):** NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发 -- **AIO (Asynchronous I/O):** AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。 - -## static 关键字 - -**static 关键字主要有以下四种使用场景:** - -1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` -2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. -3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 -4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 - -## this 关键字 - -```java -class Manager { - Employees[] employees; - - void manageEmployees() { - int totalEmp = this.employees.length; - System.out.println("Total employees: " + totalEmp); - this.report(); - } - - void report() { } -} -``` - -在上面的示例中,this关键字用于两个地方: - -- this.employees.length:访问类Manager的当前实例的变量。 -- this.report():调用类Manager的当前实例的方法。 - -此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。 - -## super 关键字 - -super关键字用于从子类访问父类的变量和方法。 例如: - -```java -public class Super { - protected int number; - - protected showNumber() { - System.out.println("number = " + number); - } -} - -public class Sub extends Super { - void bar() { - super.number = 10; - super.showNumber(); - } -} -``` - -在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber()` 方法。 - -**使用 this 和 super 要注意的问题:** - -- 在构造器中使用 `super()` 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。 -- this、super不能用在static方法中。 - -**简单解释一下:** - -被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。 - -## 深拷贝 vs 浅拷贝 - -1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。 -2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。 - -![deep and shallow copy](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/java-deep-and-shallow-copy.jpg) - -## String A = "123"; String B = new String("123");生成几个对象? - -我说如果常量池中,原来没有“123”那么就是生成了2个对象,如果常量池中有“123”那么只要1个对象生成 - -## hashTable吗,和hashmap有啥区别? - -- Hashtable的方法是安全的有synchronized修饰,Hashmap是不安全的; -- hashtable不可以有null值;HashMap则可以有空值; -- Hashtable中数组默认大小是11,而Hashmap默认大小是16,;扩容的时候hashtable是乘以2加1,而hashmap是乘以2. - -## ArrayList和LinkedList的区别? - -- ArrayList底层是数组,ArrayLIst查找数据快 -- LinkedList底层是链表,LinkedList插入删除快; - -## linkedList可以用for循环遍历吗? - -- 能不用尽量不要用,linkedList底层是链表,它使用for进行遍历,访问每一个元素都是从头开始访问然后直到找到这个元素, -- 比如说找第三个节点,需要先找到第一个节点然后找到第二个节点; -- 继续找第4个节点,不是从第三个节点开始找的,还是从第一个节点开始,所以非常的慢,不推荐,可以用迭代器进行遍历。 - -## 说说&和&&的区别 - -- &和&&都可以用作逻辑与的运算符,表示逻辑与(and) -- 当运算符两边的表达式的结果都为 true 时, 整个运算结果才为 true,否则,只要有一方为 false,则结果为 false。 -- &&还具有短路的功能,即如果第一个表达式为 false,则不再计算第二个表达式 -- &还可以用作位运算符,当&操作符两边的表达式不是 boolean 类型时,&表示按位与操作,我们通常 使用 0x0f 来与一个整数进行&运算,来获取该整数的最低 4 个 bit 位 - -## switch语句能否作用在byte上,能否作用在long上,能否作用在String上? - -- switch的condition只能是一个整数表达式或者枚举常量 -- 整数表达式则是int或者integet包装类型,由于,byte,short,char都可以隐式转换为int,则作用。 -- long 和 String 类型都不符合 switch 的语法规定,并且不能被隐式转 换成 int 类型,所以,它们不能作用于 swtich 语句中。 - -## short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1; 有什么错? - -- 对于 short s1 = 1; s1 = s1 + 1; 由于 s1+1 运算时会自动提升表达式的类型,所以结果是 int 型,再赋值 给 short 类型 s1 时,**编译器将报告需要强制转换类型的错误。** -- 对于 short s1 = 1; s1 += 1;由于 **+= 是 java 语言规定的运算符,java 编译器会对它进行特殊处理**,因此 可以正确编译。 - -## char 型变量中能不能存贮一个中文汉字?为什么? - -- char 型变量是用来存储 Unicode 编码的字符的,unicode 编码字符集中包含了汉字,所以,char 型变量 中当然可以存储汉字啦。 -- 如果某个特殊的汉字没有被包含在 unicode 编码字符集中,那么,这个 char 型变量中就不能存储这个特殊汉字。 -- unicode 编码占用两个字节,所以,char 类型的变量也是占 用两个字节。 - -## 请说出作用域 public,private,protected,以及不写时 的区别 - -| 作用域 | 当前类 | 同package | 子孙类 | 其他package | -| :-------: | :----: | :-------: | :----: | :---------: | -| public | √ | √ | √ | √ | -| protected | √ | √ | √ | × | -| friednly | √ | √ | × | × | -| private | √ | × | × | × | - -## final, finally, finalize 的区别。 - -- final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。 内部类要访问局部变量,局部变量必须定义成 final 类型 -- finally 是异常处理语句结构的一部分,表示总是执行。 -- finalize 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法, 可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM 不保证此方法总被调用 - -## 请写出你最常见到的 5 个 runtime exception。 - -- RuntimeException 的子类 -- NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。 - -## 反射 - -### 反射机制介绍 - -**Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能成为java语言的反射机制。** - -### 静态编译和动态编译 - -- 静态编译:在编译时确定类型,绑定对象 -- 动态编译:运行时确定类型,绑定对象 - -### 反射机制优缺点 - -- 优点:运行期间类型的判断,动态加载类,提高代码的灵活度。 -- 缺点:性能瓶颈:反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多。 - -### 反射的应用场景 - -**反射是框架设计的灵魂** - -在我们平时的项目开发过程中,基本上很少会直接使用的反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如**模块化**的开发,通过反射去调用对应的字节码;动态代理设计模型也采用了反射机制,还有我们日常使用的**Spring / Hibernate**等框架也大量使用到了反射机制。 - -- 我们在使用JDBC连接数据库时使用`Class.forName()`通过反射加载数据看的驱动程序; -- Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring通过XML配置模式装载Bean的过程; - - 将程序内所有XML或Properties配置文件加载入内存中; - - Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; - - 使用反射机制,根据这个字符串获得某个类的Class实例 - - 动态配置实例的属性 -- [反射参考(重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078) \ No newline at end of file diff --git a/Basics/src/com/copy/ShallowCopyDemo.java b/Basics/src/com/copy/ShallowCopyDemo.java deleted file mode 100644 index a5987f44..00000000 --- a/Basics/src/com/copy/ShallowCopyDemo.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @program JavaBooks - * @description: ShalldowCopy - * @author: mf - * @create: 2020/02/10 21:21 - */ - -package com.copy; - -public class ShallowCopyDemo { - public static void main(String[] args) throws CloneNotSupportedException { - // 原始对象 - Student student = new Student(new Subject("code"), "dream"); - System.out.println("原始对象: " + student.getName() + " - " + student.getSubject().getName()); - - // 拷贝对象 - Student cloneStu = (Student) student.clone(); - System.out.println("拷贝对象: " + cloneStu.getName() + " - " + cloneStu.getSubject().getName()); - - // 原始对象和拷贝对象是否一样: - System.out.println("原始对象和拷贝对象是否一样: " + (student == cloneStu)); - - // 原始对象和拷贝对象的name属性是否一样 - System.out.println("原始对象和拷贝对象的name属性是否一样: " + (student.getName() == cloneStu.getName())); - - // 原始对象和拷贝对象的subj属性是否一样 - System.out.println("原始对象和拷贝对象的subj属性是否一样: " + (student.getSubject() == cloneStu.getSubject())); - - student.setName("cat"); - student.getSubject().setName("eat"); - System.out.println("更新后的原始对象: " + student.getName() + " - " + student.getSubject().getName()); - System.out.println("更新原始对象后的克隆对象: " + cloneStu.getName() + " - " + cloneStu.getSubject().getName()); - - // 在这个例子中,让要拷贝的类Student实现了Clonable接口并重写Object类的clone()方法,然后在方法内部调用super.clone()方法。 - // 从输出结果中我们可以看到,对原始对象stud的"name"属性所做的改变并没有影响到拷贝对象clonedStud; - // 但是对引用对象subj的"name"属性所做的改变影响到了拷贝对象clonedStud。 - } -} diff --git a/Basics/src/com/copy/Student.java b/Basics/src/com/copy/Student.java deleted file mode 100644 index 64453203..00000000 --- a/Basics/src/com/copy/Student.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @program JavaBooks - * @description: Student - * @author: mf - * @create: 2020/02/10 21:22 - */ - -package com.copy; - -public class Student implements Cloneable{ - - // 对象的引用 - private Subject subject; - - private String name; - - public Student(Subject s, String name) { - this.subject = s; - this.name = name; - } - - public Subject getSubject() { - return subject; - } - - public String getName() { - return name; - } - - public void setSubject(Subject subject) { - this.subject = subject; - } - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Student{" + - "subject=" + subject + - ", name='" + name + '\'' + - '}'; - } - - @Override - protected Object clone() throws CloneNotSupportedException { - // 浅拷贝 -// return super.clone(); - - // 深拷贝 - Student student = new Student(new Subject(subject.getName()), name); - return student; - // 因为它是深拷贝,所以你需要创建拷贝类的一个对象。 - // 因为在Student类中有对象引用,所以需要在Student类中实现Cloneable接口并且重写clone方法。 - } -} diff --git a/Basics/src/com/copy/Subject.java b/Basics/src/com/copy/Subject.java deleted file mode 100644 index 92982084..00000000 --- a/Basics/src/com/copy/Subject.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: Subject - * @author: mf - * @create: 2020/02/10 21:22 - */ - -package com.copy; - -public class Subject { - - private String name; - - public Subject(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Subject{" + - "name='" + name + '\'' + - '}'; - } -} diff --git a/Basics/src/com/equal/Student.java b/Basics/src/com/equal/Student.java deleted file mode 100644 index 4736b8c5..00000000 --- a/Basics/src/com/equal/Student.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @program JavaBooks - * @description: Student - * @author: mf - * @create: 2020/02/07 02:07 - */ - -package com.equal; - -import java.util.HashSet; -import java.util.Objects; - -/** - * HashCode、equals和== - */ -public class Student { - - private Long id; - - private String name; - - public Student(Long id, String name) { - this.id = id; - this.name = name; - } - - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Student{" + - "id=" + id + - ", name='" + name + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Student student = (Student) o; - System.out.println("name:" + this.getName() + "-->equals:" + - (Objects.equals(id, student.id) && Objects.equals(name, student.name)) + " " + student.getName()); - return Objects.equals(id, student.id) && - Objects.equals(name, student.name); - } - - @Override - public int hashCode() { - System.out.println("name:" + this.getName() + "-->hashcode:" + Objects.hash(id, name)); - return Objects.hash(id, name); - } - - - public static void main(String[] args) { - Student student = new Student(1L, "Mai"); - Student student1 = new Student(1L, "Mai"); -// System.out.println(student == student1); -// System.out.println(student.equals(student1)); - Student student2 = new Student(2L, "Liu"); - HashSet set = new HashSet<>(); - set.add(student); - set.add(student1); - set.add(student2); - System.out.println(set.toString()); - } -} diff --git a/Basics/src/com/java8/LambdaDemo.java b/Basics/src/com/java8/LambdaDemo.java deleted file mode 100644 index 6c159b00..00000000 --- a/Basics/src/com/java8/LambdaDemo.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @program JavaBooks - * @description: Lambda表达式 - * @author: mf - * @create: 2020/01/23 16:20 - */ - -package com.java8; - -import java.util.*; - -public class LambdaDemo { - public static void main(String[] args) { - // 老版本的排序 - List names = Arrays.asList("peter", "anna", "mike", "xenia"); - System.out.println(names); -// Collections.sort(names, new Comparator() { -// @Override -// public int compare(String a, String b) { -// return b.compareTo(a); -// } -// }); - - // 新版本 lambda表达式: - -// Collections.sort(names, (String a, String b) -> { -// return b.compareTo(a); -// }); -// Collections.sort(names, (String a, String b) -> b.compareTo(a)); - names.sort((String a, String b) -> b.compareTo(a)); - System.out.println(names); - } -} diff --git a/Basics/src/com/java8/MapsDemo.java b/Basics/src/com/java8/MapsDemo.java deleted file mode 100644 index eaca1551..00000000 --- a/Basics/src/com/java8/MapsDemo.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @program JavaBooks - * @description: map - * @author: mf - * @create: 2020/01/25 12:48 - */ - -package com.java8; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class MapsDemo { - public static void main(String[] args) { -// Map map = new HashMap<>(); -// for (int i = 0; i < 10; i++) { -// map.put(i, "val" + i); -// } -// map.forEach((id, val) -> System.out.println(val)); - - Student s1 = new Student(1L, "肖战", 15); - Student s2 = new Student(2L, "王一博", 15); - Student s3 = new Student(3L, "杨紫", 17); - Student s4 = new Student(4L, "李现", 17); - List students = new ArrayList<>(); - students.add(s1); - students.add(s2); - students.add(s3); - students.add(s4); - - List names = students.stream().map(s -> "名字:" + s.getName()).collect(Collectors.toList()); - names.forEach(a -> System.out.println(a)); - } -} diff --git a/Basics/src/com/java8/ParallerStreamDemo.java b/Basics/src/com/java8/ParallerStreamDemo.java deleted file mode 100644 index 8fbce412..00000000 --- a/Basics/src/com/java8/ParallerStreamDemo.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @program JavaBooks - * @description: 并行计算 - * @author: mf - * @create: 2020/01/25 12:40 - */ - -package com.java8; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -public class ParallerStreamDemo { - - public static void main(String[] args) { - int max = 1000000; - List values = new ArrayList<>(max); - for (int i = 0; i < max; i++) { - UUID uuid = UUID.randomUUID(); - values.add(uuid.toString()); - } - - // Sequential Sort(串行排序) - long t0 = System.nanoTime(); -// long t0 = System.currentTimeMillis(); - long count = values.stream().sorted().count(); - System.out.println(count); - long t1 = System.nanoTime(); -// long t1 = System.currentTimeMillis(); - long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); -// long millis = t1 - t0; - System.out.println(String.format("sequential sort took: %d ms", millis)); - - // paraller sorted - - long t2 = System.nanoTime(); - long count1 = values.parallelStream().sorted().count(); - System.out.println(count1); - long t3 = System.nanoTime(); - long millis1 = TimeUnit.NANOSECONDS.toMillis(t3 - t2); - System.out.println(String.format("paraller sort took: %d ms", millis1)); - } -} diff --git a/Basics/src/com/java8/Person.java b/Basics/src/com/java8/Person.java deleted file mode 100644 index f199e208..00000000 --- a/Basics/src/com/java8/Person.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @program JavaBooks - * @description: 方法和函数式引用 - * @author: mf - * @create: 2020/01/23 16:36 - */ - -package com.java8; - -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; - -public class Person { - - String name; - - Integer age; - - public Person() { - } - - public Person(String name) { - this.name = name; - } - - public Person(String name, Integer age) { - this.name = name; - this.age = age; - } - - - @Override - public String toString() { - return "Person{" + - "name='" + name + '\'' + - ", age=" + age + - '}'; - } - - public static void main(String[] args) { - // 当只有1个参数时 - Supplier supplier = Person::new; // 构造函数引用指向默认的Apple()构造函数 - Person person = supplier.get(); // new 一个对象 - System.out.println(person); - - Supplier supplier1 = () -> new Person(); // 和上面等价 - Person person1 = supplier.get(); - System.out.println(person1); - - // 当只有一个参数时 - Function function = Person::new; - Person person2 = function.apply("mai"); - System.out.println(person2); - - // 当有两个参数时 - BiFunction function1 = Person::new; - Person person3 = function1.apply("feng", 25); - System.out.println(person3); - } -} diff --git a/Basics/src/com/java8/Student.java b/Basics/src/com/java8/Student.java deleted file mode 100644 index 75f255e8..00000000 --- a/Basics/src/com/java8/Student.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @program JavaBooks - * @description: Student - * @author: mf - * @create: 2020/02/07 02:07 - */ - -package com.java8; - -import java.util.HashSet; -import java.util.Objects; - -/** - * HashCode、equals和== - */ -public class Student { - - private Long id; - - private String name; - - private Integer age; - - public Student(Long id, String name, Integer age) { - this.id = id; - this.name = name; - this.age = age; - } - - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getAge() { - return age; - } - - public void setAge(Integer age) { - this.age = age; - } - - @Override - public String toString() { - return "Student{" + - "id=" + id + - ", name='" + name + '\'' + - ", age=" + age + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Student student = (Student) o; - System.out.println("name:" + this.getName() + "-->equals:" + - (Objects.equals(id, student.id) && Objects.equals(name, student.name)) + " " + student.getName()); - return Objects.equals(id, student.id) && - Objects.equals(name, student.name) && - Objects.equals(age, student.age); - } - - @Override - public int hashCode() { - System.out.println("name:" + this.getName() + "-->hashcode:" + Objects.hash(id, name)); - return Objects.hash(id, name, age); - } - - - public static void main(String[] args) { - Student student = new Student(1L, "Mai", 16); - Student student1 = new Student(1L, "Mai", 16); -// System.out.println(student == student1); -// System.out.println(student.equals(student1)); - Student student2 = new Student(2L, "Liu", 18); - HashSet set = new HashSet<>(); - set.add(student); - set.add(student1); - set.add(student2); - System.out.println(set.toString()); - } -} diff --git a/Basics/src/com/java8/streamDemo.java b/Basics/src/com/java8/streamDemo.java deleted file mode 100644 index 0b18d0d6..00000000 --- a/Basics/src/com/java8/streamDemo.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @program JavaBooks - * @description: 流的一些例子 - * @author: mf - * @create: 2020/01/23 17:39 - */ - -package com.java8; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - -public class streamDemo { - public static void main(String[] args) { - List strings = Arrays.asList("ddd3", "bbb", "ccc", "www", "eee", "ddd2", "bbb"); - - System.out.println("过滤:"); - // 过滤 - strings - .stream() - .filter((s) -> s.startsWith("d")) - .forEach(System.out::println); - - System.out.println("排序:"); - // 排序 - strings - .stream() - .sorted() - .filter((s) -> s.startsWith("d")) - .forEach(System.out::println); - - System.out.println("映射:"); - strings - .stream() - .map(String::toUpperCase) - .sorted() - .forEach(System.out::println); - - // match - System.out.println("匹配:"); - boolean anyStartsWithA = - strings - .stream() - .anyMatch((s) -> s.startsWith("d")); - System.out.println(anyStartsWithA); - boolean allStartsWithA = - strings - .stream() - .allMatch((s) -> s.startsWith("d")); - System.out.println(allStartsWithA); - boolean noneStartsWithA = - strings - .stream() - .noneMatch((s) -> s.startsWith("a")); - System.out.println(noneStartsWithA); - - System.out.println("计数:"); - long count = - strings - .stream() - .filter((s) -> s.startsWith("d")) - .count(); - System.out.println(count); - - //规约 - System.out.println("规约"); - Optional reduce = strings - .stream() - .sorted() - .reduce((s1, s2) -> s1 + "#" + s2); - reduce.ifPresent(System.out::println); - - // 去重 - System.out.println("去重"); - strings.stream().distinct().forEach(s -> System.out.println(s)); - - // 字符串拼接 - String reduce1 = Stream.of("A", "B", "C").reduce("", String::concat); - System.out.println(reduce1); - // 求最小值 - Double reduce2 = Stream.of(-1.5, 1.0, -3.0).reduce(Double.MAX_VALUE, Double::min); - System.out.println(reduce2); - // 求和 - Integer reduce3 = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); - System.out.println(reduce3); - // 求和 - Optional reduce4 = Stream.of(1, 2, 3, 4).reduce(Integer::sum); - System.out.println(reduce4.get()); - - - } -} diff --git a/Basics/src/com/pack/IntegerPackDemo.java b/Basics/src/com/pack/IntegerPackDemo.java deleted file mode 100644 index a6ce9659..00000000 --- a/Basics/src/com/pack/IntegerPackDemo.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @program JavaBooks - * @description: IntegerPackDemo - * @author: mf - * @create: 2020/02/12 00:08 - */ - -package com.pack; - -/** - * 所有整型包装类对象值的比较必须使用equals方法。 - */ -public class IntegerPackDemo { - public static void main(String[] args) { - Integer x = 3; // 装箱 - int z = x; // 拆箱 - Integer y = 3; - System.out.println(x == y); // true - // 当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时, - // 会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。 - // 所以上述代码中,x和y引用的是相同的Integer对象。 - Integer a = new Integer(3); - Integer b = new Integer(3); - System.out.println(a == b); // false - System.out.println(a.equals(b)); // true - - // 可以看一下整形包装类的equals的源码 - /** - * public boolean equals(Object obj) { - * if (obj instanceof Integer) { - * // 返回参数中的Integer的value,所以最后的比的是内容内容 - * return value == ((Integer)obj).intValue(); - * } - * return false; - * } - */ - - // 缓存池 - // new Integer(123) 与 Integer.valueOf(123) - // new Integer(123) 每次都会新建一个对象 - // Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。前提注意范围 - Integer ab = new Integer(123); - Integer ba = new Integer(123); - System.out.println(ab == ba); // false - // 从缓存中拿123 - Integer aa = Integer.valueOf(123); - Integer bb = Integer.valueOf(123); - System.out.println(aa == bb); // true - /** - * - * public static Integer valueOf(int i) { - * // 判断是否在Integer的范围内 - * if (i >= IntegerCache.low && i <= IntegerCache.high) - * return IntegerCache.cache[i + (-IntegerCache.low)]; - * return new Integer(i); - * } - */ - } -} diff --git a/Basics/src/com/reflect/ConstructorsDemo.java b/Basics/src/com/reflect/ConstructorsDemo.java deleted file mode 100644 index 7cd02529..00000000 --- a/Basics/src/com/reflect/ConstructorsDemo.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @program JavaBooks - * @description: ConstructorsDemo - * @author: mf - * @create: 2020/02/09 17:28 - */ - -package com.reflect; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -/** - * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员; - * - */ -public class ConstructorsDemo { - - public static void main(String[] args) throws Exception { - // 1. 加载Class对象 - Class clazz = Class.forName("com.reflect.Student"); - - // 2. 获取所有公有构造方法 - System.out.println("**********************所有公有构造方法*********************************"); - Constructor[] constructors = clazz.getConstructors(); - for (Constructor constructor : constructors) { - System.out.println(constructor); - } - - // 3. - System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************"); - Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); - for (Constructor declaredConstructor : declaredConstructors) { - System.out.println(declaredConstructor); - } - - // 4. - System.out.println("*****************获取公有、无参的构造方法*******************************"); - Constructor constructor = clazz.getConstructor(); - System.out.println(constructor); - - // 调用构造方法 - Object object = constructor.newInstance(); - System.out.println(object); - - // - System.out.println("******************获取私有构造方法,并调用*******************************"); - Constructor constructor1 = clazz.getDeclaredConstructor(char.class); - System.out.println(constructor1); - // 调用构造方法 - constructor1.setAccessible(true); // 暴力访问 - Object object2 = constructor1.newInstance('买'); - System.out.println(object2); - } -} diff --git a/Basics/src/com/reflect/FieldDemo.java b/Basics/src/com/reflect/FieldDemo.java deleted file mode 100644 index 5c182c38..00000000 --- a/Basics/src/com/reflect/FieldDemo.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @program JavaBooks - * @description: FieldDemo - * @author: mf - * @create: 2020/02/09 20:08 - */ - -package com.reflect; - -import java.lang.reflect.Field; - -public class FieldDemo { - public static void main(String[] args) throws Exception { - // 1. 获取class对象 - Class clazz = Class.forName("com.reflect.Student"); - - // 2. 获取所有字段 - System.out.println("************获取所有公有的字段********************"); - Field[] fields = clazz.getFields(); - for (Field field : fields) { - System.out.println(field); - } - // - System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************"); - Field[] fields1 = clazz.getDeclaredFields(); - for (Field field : fields1) { - System.out.println(field); - } - // - System.out.println("*************获取公有字段**并调用***********************************"); - Field gender = clazz.getField("gender"); - System.out.println(gender); - // 获取一个对象 - Object o = clazz.getConstructor().newInstance(); - gender.set(o, "男"); - Student stu = (Student) o; - System.out.println("验证性别:" + stu.getGender()); - // - System.out.println("*************获取公有字段**并调用***********************************"); - Field name = clazz.getDeclaredField("name"); - System.out.println(name); - name.setAccessible(true); //暴力反射,解除私有限定 - name.set(o, "买"); - System.out.println("验证姓名:" + stu); - - - } -} diff --git a/Basics/src/com/reflect/MethodDemo.java b/Basics/src/com/reflect/MethodDemo.java deleted file mode 100644 index 835c90a5..00000000 --- a/Basics/src/com/reflect/MethodDemo.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @program JavaBooks - * @description: MethodDemo - * @author: mf - * @create: 2020/02/09 23:45 - */ - -package com.reflect; - -import java.lang.reflect.Method; - -public class MethodDemo { - public static void main(String[] args) throws Exception { - // 1. 获取对象 - Class clazz = Class.forName("com.reflect.Student"); - // 2. 获取所有公有方法 - System.out.println("***************获取所有的”公有“方法*******************"); - Method[] methods = clazz.getMethods(); - for (Method method : methods) { - System.out.println(method); - } - System.out.println("***************获取所有的方法,包括私有的*******************"); - Method[] declaredMethods = clazz.getDeclaredMethods(); - for (Method declaredMethod : declaredMethods) { - System.out.println(declaredMethod); - } - System.out.println("***************获取公有的show1()方法*******************"); - Method m = clazz.getMethod("show1", String.class); - System.out.println(m); - // 实例化对象 - Object o = clazz.getConstructor().newInstance(); - m.invoke(o, "买"); - - System.out.println("***************获取私有的show4()方法******************"); - m = clazz.getDeclaredMethod("show4", int.class); - System.out.println(m); - m.setAccessible(true); // 暴力解除 私有 - Object result = m.invoke(o, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参 - System.out.println("返回值:" + result); - } -} diff --git a/Basics/src/com/reflect/ReflectDemo.java b/Basics/src/com/reflect/ReflectDemo.java deleted file mode 100644 index 024f0a2e..00000000 --- a/Basics/src/com/reflect/ReflectDemo.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @program JavaBooks - * @description: ReflectDemo - * @author: mf - * @create: 2020/02/09 16:55 - */ - -package com.reflect; - -/** - * 获取Class对象的三种方式 - * Object ——> getClass(); - * 任何数据类型(包括基本数据类型)都有一个“静态”的class属性 - * 通过Class类的静态方法:forName(String className)(常用) - */ -public class ReflectDemo { - public static void main(String[] args) throws ClassNotFoundException { - // 第一种方式获取Class对象 - Student student = new Student(); // 这一new 产生一个Student对象,一个Class对象。 - Class studentClass = student.getClass(); - System.out.println(studentClass.getName()); // com.reflect.Student - - // 第二种方式获取Class对象 - Class studentClass2 = Student.class; - System.out.println(studentClass == studentClass2); //判断第一种方式获取的Class对象和第二种方式获取的是否是同一个 - - //第三种方式获取Class对象 - Class studentClass3 = Class.forName("com.reflect.Student"); //注意此字符串必须是真实路径,就是带包名的类路径,包名.类名 - System.out.println(studentClass3 == studentClass2); // //判断三种方式是否获取的是同一个Class对象 - - // 三种方式常用第三种,第一种对象都有了还要反射干什么。 - // 第二种需要导入类的包,依赖太强,不导包就抛编译错误。 - // 一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。 - } -} diff --git a/Basics/src/com/reflect/Student.java b/Basics/src/com/reflect/Student.java deleted file mode 100644 index e9c374a1..00000000 --- a/Basics/src/com/reflect/Student.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @program JavaBooks - * @description: Student - * @author: mf - * @create: 2020/02/09 16:55 - */ - -package com.reflect; - -public class Student { - - private String name; - - private Integer age; - - public String gender; - - // 无参数的构造方法 - public Student() { - System.out.println("调用了公有、无参构造方法执行了。。。"); - } - - // 默认的构造方法 - public Student(String str) { - System.out.println("(默认)的构造方法 s = " + str); - } - - // 有一个参数的构造方法 - public Student(char name) { - System.out.println("姓名:" + name); - } - - // 有多个参数的构造方法 - public Student(String name, Integer age) { - this.name = name; - this.age = age; - } - - //受保护的构造方法 - protected Student(boolean n){ - System.out.println("受保护的构造方法 n = " + n); - } - - //私有构造方法 - private Student(int age){ - System.out.println("私有的构造方法 年龄:"+ age); - } - - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getAge() { - return age; - } - - public void setAge(Integer age) { - this.age = age; - } - - public String getGender() { - return gender; - } - - public void setGender(String gender) { - this.gender = gender; - } - - //**************成员方法***************// - public void show1(String s){ - System.out.println("调用了:公有的,String参数的show1(): s = " + s); - } - protected void show2(){ - System.out.println("调用了:受保护的,无参的show2()"); - } - void show3(){ - System.out.println("调用了:默认的,无参的show3()"); - } - private String show4(int age){ - System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age); - return "abcd"; - } - - @Override - public String toString() { - return "Student{" + - "name='" + name + '\'' + - ", age=" + age + - ", gender='" + gender + '\'' + - '}'; - } -} diff --git a/Basics/src/com/strings/SIntern.java b/Basics/src/com/strings/SIntern.java deleted file mode 100644 index 3bc3fb8a..00000000 --- a/Basics/src/com/strings/SIntern.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: String.Intern - * @author: mf - * @create: 2020/02/12 01:35 - */ - -package com.strings; - -/** - * 使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。 - */ -public class SIntern { - public static void main(String[] args) { - // 采用new String创建对象 - String s1 = new String("aaa"); - String s2 = new String("aaa"); - System.out.println(s1 == s2); // false - - // 将s1的对象通过intern放到String Pool中 - String s3 = s1.intern(); - System.out.println(s1.intern() == s3); // true - System.out.println(s1.intern() == s2.intern()); // true - System.out.println(s3 == "aaa"); // true - - - // 如果是采用 "bbb" 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Pool 中。 - String s4 = "bbb"; - String s5 = "bbb"; - System.out.println(s4 == s5); // true - } -} diff --git a/Basics/src/com/strings/SbDemo.java b/Basics/src/com/strings/SbDemo.java deleted file mode 100644 index 7b2b2db8..00000000 --- a/Basics/src/com/strings/SbDemo.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @program JavaBooks - * @description: String StringBuffer StringBuilder - * @author: mf - * @create: 2020/02/07 19:13 - */ - -package com.strings; - -public class SbDemo { - public static void main(String[] args) { - // String - String str = "hello"; - long start = System.currentTimeMillis(); - for (int i = 0; i < 100000; i++) { - str += i; // 创建多少个对象,, - } - System.out.println("String: " + (System.currentTimeMillis() - start)); - - // StringBuffer - StringBuffer sb = new StringBuffer("hello"); - long start1 = System.currentTimeMillis(); - for (int i = 0; i < 1000000; i++) { - sb.append(i); - } - System.out.println("StringBuffer: " + (System.currentTimeMillis() - start1)); - - - // StringBuilder - StringBuilder stringBuilder = new StringBuilder("hello"); - long start2 = System.currentTimeMillis(); - for (int i = 0; i < 1000000; i++) { - stringBuilder.append(i); - } - System.out.println("StringBuilder: " + (System.currentTimeMillis() - start2)); - } -} diff --git a/Basics/src/com/transfer/Student.java b/Basics/src/com/transfer/Student.java deleted file mode 100644 index 114f7af9..00000000 --- a/Basics/src/com/transfer/Student.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: Student - * @author: mf - * @create: 2020/02/07 22:01 - */ - -package com.transfer; - -public class Student { - - private String name; - - public Student(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Student{" + - "name='" + name + '\'' + - '}'; - } -} diff --git a/Basics/src/com/transfer/TransferDemo.java b/Basics/src/com/transfer/TransferDemo.java deleted file mode 100644 index 4a940e73..00000000 --- a/Basics/src/com/transfer/TransferDemo.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @program JavaBooks - * @description: TransferDemo - * @author: mf - * @create: 2020/02/07 21:22 - */ - -package com.transfer; - -/** - * Java 程序设计语言总是采用按值调用。 - * 也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。 - */ -public class TransferDemo { - public static void main(String[] args) { - int num1 = 10; - int num2 = 20; - - swap(num1, num2); - System.out.println("num1 = " + num1); - System.out.println("num2 = " + num2); - // 通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 - - - - - } - - - - private static void swap(int a, int b) { - int temp = a; - a = b; - b = temp; - System.out.println("a = " + a); - System.out.println("b = " + b); - } -} - - diff --git a/Basics/src/com/transfer/TransferDemo2.java b/Basics/src/com/transfer/TransferDemo2.java deleted file mode 100644 index a5733624..00000000 --- a/Basics/src/com/transfer/TransferDemo2.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @program JavaBooks - * @description: TransferDemo2 - * @author: mf - * @create: 2020/02/07 22:04 - */ - -package com.transfer; - -public class TransferDemo2 { - - public static void main(String[] args) { - int[] arr = { 1, 2, 3, 4, 5 }; - System.out.println(arr[0]); - change(arr); - System.out.println(arr[0]); - // 法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。 - } - - private static void change(int[] array) { - // 修改数组中的一个元素 - array[0] = 0; - } -} diff --git a/Basics/src/com/transfer/TransferDemo3.java b/Basics/src/com/transfer/TransferDemo3.java deleted file mode 100644 index 4417f4ed..00000000 --- a/Basics/src/com/transfer/TransferDemo3.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @program JavaBooks - * @description: TransferDemo3 - * @author: mf - * @create: 2020/02/07 22:05 - */ - -package com.transfer; - -public class TransferDemo3 { - public static void main(String[] args) { - // 有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。 - Student s1 = new Student("Mai"); - Student s2 = new Student("Feng"); - swap2(s1, s2); - System.out.println("s1:" + s1.getName()); - System.out.println("s2:" + s2.getName()); - // 方法并没有改变存储在变量 s1 和 s2 中的对象引用。 - // swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝 - } - - private static void swap2(Student x, Student y) { - Student temp = x; - x = y; - y = temp; - System.out.println("x:" + x.getName()); - System.out.println("y:" + y.getName()); - } -} diff --git a/Basics/src/com/type/IntegerTest.java b/Basics/src/com/type/IntegerTest.java deleted file mode 100644 index 90c4423b..00000000 --- a/Basics/src/com/type/IntegerTest.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @program JavaBooks - * @description: 模拟变量在内存的布局 - * @author: mf - * @create: 2020/03/20 13:30 - */ - -package com.type; - -public class IntegerTest { - int value = 1; - String s = "买"; - int a = 257; -} diff --git a/Basics/src/com/type/TypeConvert.java b/Basics/src/com/type/TypeConvert.java deleted file mode 100644 index 2407ca7a..00000000 --- a/Basics/src/com/type/TypeConvert.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @program JavaBooks - * @description: TypeConvert - * @author: mf - * @create: 2020/02/12 01:46 - */ - -package com.type; - -/** - * 隐士转换 - */ -public class TypeConvert { - public static void main(String[] args) { - // 字面量属于 double 类型 - // 不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型 - // Java 不能隐式执行向下转型,因为这会使得精度降低。 - // float f = 1.1; - float f = 1.1f; - - // 因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。 - short s1 = 1; - // s1 = s1 + 1; - - // 但是使用 += 运算符可以执行隐式类型转换。 - s1 += 1; - // 上面的语句相当于将 s1 + 1 的计算结果进行了向下转型: - s1 = (short) (s1 + 1); - - } -} diff --git "a/Collections/HashMap-ConcurrentHashMap\351\235\242\350\257\225\345\277\205\351\227\256.md" "b/Collections/HashMap-ConcurrentHashMap\351\235\242\350\257\225\345\277\205\351\227\256.md" deleted file mode 100644 index 74a4c212..00000000 --- "a/Collections/HashMap-ConcurrentHashMap\351\235\242\350\257\225\345\277\205\351\227\256.md" +++ /dev/null @@ -1,477 +0,0 @@ -## 引言 - -**HashMap 和 ConcurrentHashMap面试常问,务必理解和掌握** - -## HashMap - -众所周知,HashMap的底层结构是**数组和链表**组成的,不过在jdk1.7和jdk1.8中具体实现略有不同。 - - - -### jdk1.7 - -先看图![](https://i.loli.net/2019/05/08/5cd1d2be77958.jpg) - -再看看1.7的实现![](https://i.loli.net/2019/05/08/5cd1d2bfd6aba.jpg) - -介绍成员变量: - -- 初始化桶大小,因为底层是数组,所以这是数组默认的大小。 -- 桶最大值。 -- 默认的负载因子(0.75) -- table真正存放数据的数组。 -- map存放数量的大小 -- 桶大小,可在构造函数时显式指定。 -- 负载因子,可在构造函数时显式指定。 - -#### 负载因子 - -源代码 - -```java -public HashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); // 桶和负载因子 -} -public HashMap(int initialCapacity, float loadFactor) { - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal initial capacity: " + - initialCapacity); - if (initialCapacity > MAXIMUM_CAPACITY) - initialCapacity = MAXIMUM_CAPACITY; - if (loadFactor <= 0 || Float.isNaN(loadFactor)) - throw new IllegalArgumentException("Illegal load factor: " + - loadFactor); - this.loadFactor = loadFactor; - threshold = initialCapacity; - init(); -} -``` - -- 给定的默认容量为16,负载因子为0.75. -- Map在使用过程中不断的往里面存放数据,当数量达到了`16 * 0.75 = 12`就需要将当前16的容量进行扩容,而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 -- 因此通常建议能提前预估HashMap的大小最好,尽量的减少扩容带来的额外性能损耗。 - -#### Entry - -`transient Entry[] table = (Entry[]) EMPTY_TABLE;` - -如何定义呢?![](https://i.loli.net/2019/05/08/5cd1d2c08e693.jpg) - -Entry是Hashmap中的一个内部类,从他的成员变量很容易看出: - -- key就是写入时的键 -- value自然就是值 -- 开始的时候就提到HashMap是由数组和链表组成,所以这个next就是用于实现链表结构 -- hash存放的是当前key的hashcode - -#### put - -```java -public V put(K key, V value) { - if (table == EMPTY_TABLE) { - inflateTable(threshold); // 判断数组是否需要初始化 - } - if (key == null) - return putForNullKey(value); // 判断key是否为空 - int hash = hash(key); // 计算hashcode - int i = indexFor(hash, table.length); // 计算桶 - for (Entry e = table[i]; e != null; e = e.next) { - Object k; - if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // 遍历判断链表中的key和hashcode是否相等,等就替换 - V oldValue = e.value; - e.value = value; - e.recordAccess(this); - return oldValue; - } - } - modCount++; - addEntry(hash, key, value, i); // 没有就添加新的呗 - return null; -} -``` - -- 判断当前数组是否需要初始化 -- 如果key为空,则put一个空值进去 -- 根据key计算hashcode -- 根据计算的hashcode定位index的桶 -- 如果桶是一个链表,则需要遍历判断里面的hashcode、key是否和传入的key相等,如果相等则进行覆盖,并返回原来的值 -- 如果桶是空的,说明当前位置没有数据存入,此时新增一个Entry对象写入当前位置。 - -```java -void addEntry(int hash, K key, V value, int bucketIndex) { - if ((size >= threshold) && (null != table[bucketIndex])) {// 是否扩容 - resize(2 * table.length); - hash = (null != key) ? hash(key) : 0; - bucketIndex = indexFor(hash, table.length); - } - createEntry(hash, key, value, bucketIndex); -} -void createEntry(int hash, K key, V value, int bucketIndex) { - Entry e = table[bucketIndex]; - table[bucketIndex] = new Entry<>(hash, key, value, e); - size++; -} -``` - -- 当调用addEntry写入Entry时需要判断是否需要扩容 -- 如果需要就进行两倍扩充,并将当前的key重新hash并定位。 -- 而在createEntry中会将当前位置的桶传入到新建的桶中,如果当前桶油值就会在位置形成链表。 - -#### get - -```java -public V get(Object key) { - if (key == null) // 判断key是否为空 - return getForNullKey(); // 为空,就返回空值 - Entry entry = getEntry(key); // get entry - return null == entry ? null : entry.getValue(); -} -final Entry getEntry(Object key) { - if (size == 0) { - return null; - } - int hash = (key == null) ? 0 : hash(key); //根据key和hashcode - for (Entry e = table[indexFor(hash, table.length)]; - e != null; - e = e.next) { - Object k; - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } - return null; -} -``` - -- 首先根据key计算hashcode,然后定位具体的桶 -- 判断该位置是否为链表 -- 不是链接就根据key和hashcode是否相等来返回值 -- 为链表则需要遍历直到key和hashcode相等就返回值 -- 啥都没得,就返回null - -### jdk1.8 - -不知道 1.7 的实现大家看出需要优化的点没有? - -其实一个很明显的地方就是链表 - -**当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 `O(N)`。** - -![](https://i.loli.net/2019/05/08/5cd1d2c1c1cd7.jpg) - -看看成员变量 - -```java -static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 -/** - * The maximum capacity, used if a higher value is implicitly specified - * by either of the constructors with arguments. - * MUST be a power of two <= 1<<30. - */ -static final int MAXIMUM_CAPACITY = 1 << 30; -/** - * The load factor used when none specified in constructor. - */ -static final float DEFAULT_LOAD_FACTOR = 0.75f; -static final int TREEIFY_THRESHOLD = 8; -transient Node[] table; -/** - * Holds cached entrySet(). Note that AbstractMap fields are used - * for keySet() and values(). - */ -transient Set> entrySet; -/** - * The number of key-value mappings contained in this map. - */ -transient int size; -``` - -- `TREEIFY_THRESHOLD` 用于判断是否需要将链表转换为红黑树的阈值。 -- HashEntry 修改为 Node。 -- Node 的核心组成其实也是和 1.7 中的 HashEntry 一样,存放的都是 `key value hashcode next` 等数据。 - -#### put - -![](https://i.loli.net/2019/05/08/5cd1d2c378090.jpg) - -- 判断当前桶是否为空,空的就需要初始化(resize中会判断是否进行初始化) -- 根据当前key的hashcode定位到具体的桶中并判断是否为空,为空则表明没有Hash冲突,就直接在当前位置创建一个新桶 -- 如果当前桶油值(Hash冲突),那么就要比较当前桶中的key、key的hashcode与写入的key是否相等,相等就赋值给e,在第8步的时候会统一进行赋值及返回 -- 如果当前桶为红黑树,那就要按照红黑树的方式写入数据 -- 如果是个链表,就需要将当前的key、value封装称一个新节点写入到当前桶的后面形成链表。 -- 接着判断当前链表的大小是否大于预设的阈值,大于就要转换成为红黑树 -- 如果在遍历过程中找到key相同时直接退出遍历。 -- 如果`e != null`就相当于存在相同的key,那就需要将值覆盖。 -- 最后判断是否需要进行扩容。 - -#### get - -```java -public V get(Object key) { - Node e; - return (e = getNode(hash(key), key)) == null ? null : e.value; -} -final Node getNode(int hash, Object key) { - Node[] tab; Node first, e; int n; K k; - if ((tab = table) != null && (n = tab.length) > 0 && - (first = tab[(n - 1) & hash]) != null) { - if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) - return first; - if ((e = first.next) != null) { - if (first instanceof TreeNode) - return ((TreeNode)first).getTreeNode(hash, key); - do { - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } while ((e = e.next) != null); - } - } - return null; -} -``` - -- 首先将key hash之后取得所定位的桶 -- 如果桶为空,则直接返回null -- 否则判断桶的第一个位置(有可能是链表、红黑树)的key是否为查询的key,是就直接返回value -- 如果第一个不匹配,则判断它的下一个是红黑树还是链表 -- 红黑树就按照树的查找方式返回值 -- 不然就按照链表的方式遍历匹配返回值 - -**从这两个核心方法(get/put)可以看出 1.8 中对大链表做了优化,修改为红黑树之后查询效率直接提高到了 `O(logn)`。** - -## 问题 - -但是 HashMap 原有的问题也都存在,比如在并发场景下使用时容易出现死循环。 - -```java -final HashMap map = new HashMap(); -for (int i = 0; i < 1000; i++) { - new Thread(new Runnable() { - @Override - public void run() { - map.put(UUID.randomUUID().toString(), ""); - } - }).start(); -} -``` - -- HashMap扩容的时候会调用resize()方法,就是这里的并发操作容易在一个桶上形成环形链表 -- 这样当获取一个不存在的key时,计算出的index正好是环形链表的下标就会出现死循环。 -- 但是1.7的头插法造成的问题,1.8改变了插入顺序,就解决了这个问题,但是为了内存可见性等安全性,还是需要ConCurrentHashMap - -![](https://i.loli.net/2019/05/08/5cd1d2c4ede54.jpg) - -## 遍历方式 - -还有一个值得注意的是 HashMap 的遍历方式,通常有以下几种: - -```java -Iterator> entryIterator = map.entrySet().iterator(); - while (entryIterator.hasNext()) { - Map.Entry next = entryIterator.next(); - System.out.println("key=" + next.getKey() + " value=" + next.getValue()); - } - -Iterator iterator = map.keySet().iterator(); - while (iterator.hasNext()){ - String key = iterator.next(); - System.out.println("key=" + key + " value=" + map.get(key)); - } -``` - -- 建议使用第一种,同时可以把key value取出。 -- 第二种还需要通过key取一次key,效率较低。 - -## ConCurrentHashMap - -### jdk1.7 - -![](https://i.loli.net/2019/05/08/5cd1d2c5ce95c.jpg) - -- Segment数组 -- HashEntry组成 -- 和HashMap一样,仍然是数组加链表 - -```java -/** - * Segment 数组,存放数据时首先需要定位到具体的 Segment 中。 - */ -final Segment[] segments; -transient Set keySet; -transient Set> entrySet; -``` - -Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下: - -```java -static final class Segment extends ReentrantLock implements Serializable { - private static final long serialVersionUID = 2249069246763182397L; - - // 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶 - transient volatile HashEntry[] table; - transient int count; - transient int modCount; - transient int threshold; - final float loadFactor; - -} -``` - -![](https://i.loli.net/2019/05/08/5cd1d2c635c69.jpg) - -- 唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 -- ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。 -- 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 -- 每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。 - -#### put - -```java -public V put(K key, V value) { - Segment s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key); - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck - (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment - s = ensureSegment(j); - return s.put(key, hash, value, false); -} -``` - -- 通过key定位到Segment,之后在对应的Segment中进行具体的put - -```java -final V put(K key, int hash, V value, boolean onlyIfAbsent) { - HashEntry node = tryLock() ? null : - scanAndLockForPut(key, hash, value); - V oldValue; - try { - HashEntry[] tab = table; - int index = (tab.length - 1) & hash; - HashEntry first = entryAt(tab, index); - for (HashEntry e = first;;) { - if (e != null) { - K k; - if ((k = e.key) == key || - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; - ++modCount; - } - break; - } - e = e.next; - } - else { - if (node != null) - node.setNext(first); - else - node = new HashEntry(hash, key, value, first); - int c = count + 1; - if (c > threshold && tab.length < MAXIMUM_CAPACITY) - rehash(node); - else - setEntryAt(tab, index, node); - ++modCount; - count = c; - oldValue = null; - break; - } - } - } finally { - unlock(); - } - return oldValue; -} -``` - -- 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 -- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` 自旋获取锁。 - -![](https://i.loli.net/2019/05/08/5cd1d2cc3c982.jpg) - -- 尝试获取自旋锁 -- 如果重试的次数达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 - -![](https://i.loli.net/2019/05/08/5cd1d2cd25c37.jpg) - -- 将当前的Segment中的table通过key的hashcode定位到HashEntry -- 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value -- 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 -- 最后会解除在1中所获取当前Segment的所。 - -#### get方法 - -```java -public V get(Object key) { - Segment s; // manually integrate access methods to reduce overhead - HashEntry[] tab; - int h = hash(key); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return e.value; - } - } - return null; -} -``` - -- 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 -- 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。 -- ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁**。 - -### jdk1.8 - -**那就是查询遍历链表效率太低。** - -![](https://i.loli.net/2019/05/08/5cd1d2ce33795.jpg) - -**其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性** - -![](https://i.loli.net/2019/05/08/5cd1d2ceebe02.jpg) - -- 也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。 -- 其中的 `val next` 都用了 volatile 修饰,保证了可见性。 - -#### put - -![](https://i.loli.net/2019/05/08/5cd1d2cfc3293.jpg) - -- 根据key计算出hashcode -- 判断是否需要进行初始化 -- f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功。 -- 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 -- 如果都不满足,则利用synchronized锁写入数据 -- 如果数量大于`TREEIFY_THRESHOLD` 则要转换为红黑树。 - -#### get - -![](https://i.loli.net/2019/05/08/5cd1d2d22c6cb.jpg) - -- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 -- 如果是红黑树那就按照树的方式获取值。 -- 就不满足那就按照链表的方式遍历获取值。 - -1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(`O(logn)`),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。 - -## 总结 - -套路: - -- 谈谈你理解的 HashMap,讲讲其中的 get put 过程。 -- 1.8 做了什么优化? -- 是线程安全的嘛? -- 不安全会导致哪些问题? -- 如何解决?有没有线程安全的并发容器? -- ConcurrentHashMap 是如何实现的? 1.7、1.8 实现有何不同?为什么这么做? \ No newline at end of file diff --git "a/Collections/Java\351\235\242\347\273\217-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" "b/Collections/Java\351\235\242\347\273\217-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" deleted file mode 100644 index c6c1337c..00000000 --- "a/Collections/Java\351\235\242\347\273\217-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" +++ /dev/null @@ -1,82 +0,0 @@ -## 引言 - -**Java的集合框架,包括常见的面试源码解析等...** - - - -## Collection类关系图 - -![类关系图](https://www.pdai.tech/_images/java_collections_overview.png) - -### 介绍 - -容器,就是可以容纳其他Java对象的对象。其始于JDK 1.2。 优点: - -- 降低编程难度 -- 提高程序性能 -- 提高API间的互操作性 -- 降低学习难度 -- 降低设计和实现相关API的难度 -- 增加程序的重用性 - -### Collection - -容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 - -#### Set - -##### TreeSet - -基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet查找的时间复杂为O(1),TreeSet则为O(logN)。 - -##### HashSet - -基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用Iterator遍历HashSet得到结果是不确定的。 - -##### LinkedHashSet - -具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。 - -#### List - -##### ArrayList - -基于动态数组实现,支持随机访问。 - -##### Vector - -和ArrayList类似,但它是线程安全的。 - -##### LinkedList - -基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈、队列和双向队列。 - -#### Queue - -##### LinkedList - -可以用来实现双向队列。 - -##### PriorityQueue - -基于堆结构实现,可以用它实现优先队列。 - -#### Map - -##### TreeMap - -基于红黑树实现 - -##### HashMap - -基于哈希表实现 - -##### HashTable - -- 和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。 -- 它是遗留类,不应该去使用它。 -- 现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。 - -##### LinkedHashMap - -使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 \ No newline at end of file diff --git a/Interview/.DS_Store b/Interview/.DS_Store deleted file mode 100644 index b71c7eb5..00000000 Binary files a/Interview/.DS_Store and /dev/null differ diff --git a/Interview/bus/README.md b/Interview/bus/README.md deleted file mode 100644 index c9d95da6..00000000 --- a/Interview/bus/README.md +++ /dev/null @@ -1,11 +0,0 @@ -- 项目地址:[微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) -- [环境搭建文档](Java/bus/环境搭建文档.md) -- [Redis绑定Token分析文档](Java/bus/Redis绑定Token.md) -- [用户服务所有接口分析文档](Java/bus/用户服务.md) -- [班车服务所有接口分析文档](Java/bus/班车服务.md) -- [订单服务所有接口分析文档](Java/bus/订单服务.md) -- [支付服务所有接口分析文档](Java/bus/支付服务.md) -- [添加订单、支付和退款的业务结合消息队列](Java/bus/RocketMQ最终一致性.md) -- [Redis的key过期事件结合自动取消订单业务](Java/bus/Redis的key过期事件.md) -- [SQL语句调优](Java/bus/业务逻辑SQL语句.md) -- [Zookeeper的bug之一](Java/bus/上线遇到的bug.md) \ No newline at end of file diff --git "a/Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" "b/Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" deleted file mode 100644 index 4e2167ac..00000000 --- "a/Interview/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" +++ /dev/null @@ -1,68 +0,0 @@ -# 订单自动取消 - -> 如果系统的并发量不是特别高的,或者日访问量不是特别高的话,可以使用Redis的key过期事件,如果访问量过高,还是采用延迟队列吧。 - -## Redis的配置 -``` -# -# notify-keyspace-events Ex -# -# By default all notifications are disabled because most users don't need -# this feature and the feature has some overhead. Note that if you don't -# specify at least one of K or E, no events will be delivered. -notify-keyspace-events "Ex" -``` - -## RedisListenerConfig配置 -```java -@Configuration -public class RedisListenerConfig { - @Bean - RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - return container; - } -} -``` - -## RedisKeyExpirationListener监听事件 - -```java -@Component -@Slf4j -public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { - public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { - super(listenerContainer); - } - - @Autowired - private IOrderService orderService; - - /** - * 监听key过期事件 - * @param message - * @param pattern - */ - @Override - public void onMessage(Message message, byte[] pattern) { - super.onMessage(message, pattern); - String expiredKey = message.toString(); - log.info("redis key过期:{}",expiredKey); - String orderCancleKey = RedisConstants.ORDER_CANCLE_EXPIRE.getKey(); - if (expiredKey.startsWith(orderCancleKey)) { - // 获取订单id - String[] strings = expiredKey.split(orderCancleKey); - String orderId = strings[1]; - log.warn("过期订单ID:" + orderId); - // 得到过期订单。 - // 1。 更改订单状态就完事了 - OrderRequest request = new OrderRequest(); - request.setOrderStatus("2"); // 关闭订单 - request.setUuid(Convert.toLong(orderId)); - orderService.updateOrderStatus(request); - log.warn("过期订单已处理:" + orderId); - } - } -} -``` \ No newline at end of file diff --git "a/Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" deleted file mode 100644 index be1fc864..00000000 --- "a/Interview/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 用户服务 - - -## UserController - -### checkUsername -> 这个我就不需要再说了,这个没啥可说的 - - -### register -> 这个注意请求体的参数即可,也没什么可说的 - - -### getUserById -> 注意缓存是否存在,存在直接返回即可,没有就调用业务`getUserById`获取结果 - -### updateUserInfo -> 注意请求体的参数即可,并且若是更新数据,那么自然要删除UserInfo的缓存,保持数据一致,写不写缓存这一点都行,下次请求getUserById会写缓存。 - -### logout -> 注意redis删除缓存即可 - - -## UserServiceImpl - -### checkUsername -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 查询user_name列与getUsername相匹配的数据 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -``` - -### regsiter -```java -// 密码采用了md5加密 -String md5Password = MD5Util.encrypt(user.getUserPwd()); -user.setUserPwd(md5Password); -``` - -### login -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 根据user_name查询用户是否存在 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -if (user != null && user.getUuid() > 0) { - // 用户不为空,用户id > 0 - String md5Password = MD5Util.encrypt(request.getPassword()); - if (user.getUserPwd().equals(md5Password)) { - // 数据库密码和所加密的md5密码一致 - res.setUserId(user.getUuid()); - res.setCode(SbCode.SUCCESS.getCode()); - res.setMsg(SbCode.SUCCESS.getMessage()); - } -} -``` - -### getUserById -```java -//根据id查询用户 -User user = userMapper.selectById(request.getId()); -UserDto userDto = userConverter.User2Res(user); -``` - -### updateUserInfo -```java -// 这里一采用了mapstruct映射,有兴趣的可以去网上找资料学习学习 -User user = userConverter.res2User(request); -``` - -## 总结 - -用户服务,没有什么比较复杂的业务,复杂的业务在订单服务、场次服务、支付服务、 diff --git a/Interview/classify/.DS_Store b/Interview/classify/.DS_Store deleted file mode 100644 index 45a288f1..00000000 Binary files a/Interview/classify/.DS_Store and /dev/null differ diff --git a/Interview/codes/.DS_Store b/Interview/codes/.DS_Store deleted file mode 100644 index dd7723cb..00000000 Binary files a/Interview/codes/.DS_Store and /dev/null differ diff --git "a/Interview/codes/basics/IO\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/basics/IO\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index b1a1d0d6..00000000 --- "a/Interview/codes/basics/IO\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,30 +0,0 @@ -## IO的一些问题 - -### 键盘输入 - -> 在牛客网撕代码的时候,经常获取键盘输入 - -如: -```java -Scanner sc = new Scanner(System.in); -int a = sc.nextInt() -String s = sc.nextLine(); -``` - -### IO流 -- **按照流的流向**:可以分为**输入流**和**输出流**; -- **按照操作单元**:可以划分为**字节流**和**字符流**; -- **按照流的角色**:可以分为**节点流**和**处理流**。 - -Java IO流共涉及40多个类,这些类看上去很杂乱,哥们我也记不住,就是咱一直不用,就是记不住,那咋办捏。害,烧脑... - -- `InputSream/Reader`:所有的输入流的基类,前者是字节输入流,后者是字符输入流。 -- `OutputSream/Writer`:所有的输出流的基类,前者是字节输出流,后者是字符输出流。 - -### 一直问BIO、NIO和AIO... -> 问我都有点烦了... 但是又没办法呀... -- **BIO (Blocking I/O)**:**同步阻塞I/O模式**,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 -- **NIO (New I/O)**:NIO是一种**同步非阻塞的I/O模型**,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 `Channel` , `Selector`,`Buffer`等抽象。NIO中的N可以理解为`Non-blocking`,不单纯是New。它支持**面向缓冲**的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。 -> 有兴趣的,可以学学Netty,这哥们的源码倒是挺难的... - -- **AIO (Asynchronous I/O)**: AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是**异步非阻塞的IO模型**。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。 diff --git "a/Interview/codes/basics/Java\345\200\274\344\274\240\351\200\222\347\232\204\351\227\256\351\242\230.md" "b/Interview/codes/basics/Java\345\200\274\344\274\240\351\200\222\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index b5079b3a..00000000 --- "a/Interview/codes/basics/Java\345\200\274\344\274\240\351\200\222\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,93 +0,0 @@ -## Java值传递的问题 - -> 按值调用(call by value)表示方法接收的是**调用者提供的值**,而按引用调用(call by reference)表示方法接收的是**调用者提供的变量地址**。一个方法可以修改**传递引用所对应的变量值**,而**不能修改传递值调用所对应的变量值**。 - -### 基本类型传递 - -```java -public static void main(String[] args) { - int num1 = 10; - int num2 = 20; - - swap(num1, num2); // 交换 - - System.out.println("num1 = " + num1); // 10 - System.out.println("num2 = " + num2); // 20 -} - -public static void swap(int a, int b) { - int temp = a; - a = b; - b = temp; - - System.out.println("a = " + a); // 20 - System.out.println("b = " + b); // 10 -} -// a = 20 -// b = 10 -// num1 = 10 -// num2 = 20 - -``` -整个图就更理解了嘛: -![](http://media.dreamcat.ink/uPic/%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B%E4%BC%A0%E9%80%92%20.png) -> 在 swap 方法中,**a、b 的值进行交换,并不会影响到 num1、num2**。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,**a、b 相当于 num1、num2 的副本**,副本的内容无论怎么修改,都不会影响到原件本身。 - -### 数组 -```java - public static void main(String[] args) { - int[] arr = { 1, 2, 3, 4, 5 }; - System.out.println(arr[0]); // 1 - change(arr); - System.out.println(arr[0]); // 0 - // 得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。 - } - - private static void change(int[] array) { - // 修改数组中的一个元素 - array[0] = 0; - } -// 1 -// 0 -``` -当然也可以用图助你一臂之力: -![](http://media.dreamcat.ink/uPic/%E6%95%B0%E7%BB%84%E7%B1%BB%E5%9E%8B%E4%BC%A0%E9%80%92.png) -> array 被初始化 arr 的拷贝也就是**一个对象的引用**,也就是说 array 和 arr 指向的是**同一个数组对象**。 因此,外部对引用对象的改变会反映到所对应的对象上。 - -通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,**方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象**。 - -### 再看 -```java - public static void main(String[] args) { - // 有些程序员认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。 - Student s1 = new Student("Mai"); - Student s2 = new Student("Feng"); - swap2(s1, s2); - System.out.println("s1:" + s1.getName()); // Mai - System.out.println("s2:" + s2.getName()); // Feng - // 方法并没有改变存储在变量 s1 和 s2 中的对象引用。 - // swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝 - } - - private static void swap2(Student x, Student y) { - Student temp = x; - x = y; - y = temp; - System.out.println("x:" + x.getName()); // Feng - System.out.println("y:" + y.getName()); // Mai - } -// x:Feng -// y:Mai -// s1:Mai -// s2:Feng -``` -请看图: -![](http://media.dreamcat.ink/uPic/%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92.png) - -> 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝 - -### 总结 -- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。**第一个例子** -- 一个方法可以改变一个对象参数的状态。**第二个例子** -- 一个方法不能让对象参数引用一个新的对象。**第三个例子** - diff --git "a/Interview/codes/basics/String\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/basics/String\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 03b6ec62..00000000 --- "a/Interview/codes/basics/String\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,134 +0,0 @@ -## String的一些问题 - -### String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? - -先简单聊一下它们仨 - -#### 可变性 -> 简单的来说:`String` 类中使用 `final` 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 `String` 对象是不可变的。 -> `StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串char[]value 但是没有用 `final` 关键字修饰,所以这两种对象都是可变的。 - -#### 线程安全 -> `String` 中的对象是不可变的,也就可以理解为常量,线程安全。 -> `AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。 - - -#### 性能 -> 每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。 -> `StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 - -#### 总结一波 -- 操作少量的数据: 适用String -- 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder -- 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer - -#### 举个例子 -```java -public static void main(String[] args) { - // String - String str = "hello"; - long start = System.currentTimeMillis(); - for (int i = 0; i < 100000; i++) { - str += i; // 创建多少个对象,, - } - System.out.println("String: " + (System.currentTimeMillis() - start)); - - // StringBuffer - StringBuffer sb = new StringBuffer("hello"); - long start1 = System.currentTimeMillis(); - for (int i = 0; i < 1000000; i++) { - sb.append(i); - } - System.out.println("StringBuffer: " + (System.currentTimeMillis() - start1)); - - - // StringBuilder - StringBuilder stringBuilder = new StringBuilder("hello"); - long start2 = System.currentTimeMillis(); - for (int i = 0; i < 1000000; i++) { - stringBuilder.append(i); - } - System.out.println("StringBuilder: " + (System.currentTimeMillis() - start2)); -} -``` - -### String A = "123"; String B = new String("123");生成几个对象? -> 如果常量池中,原来没有“123”那么就是生成了2个对象,如果常量池中有“123”那么只要1个对象生成 - -这个问题,简单。 但是接下来这个可以看看`String.intern()`方法 - -举个例子: -```java -public class StringTest { - public static void main(String[] args) { - String str1 = "todo"; // 常量池 - String str2 = "todo"; // 从常量池找了str1 - String str3 = "to"; // 常量池 - String str4 = "do"; // 常量池 - String str5 = str3 + str4; // 内部用StringBuilder拼接了一波。 因此, 并非常量池 - String str6 = new String(str1); // 创建对象了, 那还能是常量池的引用? - - System.out.println("------普通String测试结果------"); - System.out.print("str1 == str2 ? "); - System.out.println( str1 == str2); // true - System.out.print("str1 == str5 ? "); - System.out.println(str1 == str5); // false - System.out.print("str1 == str6 ? "); - System.out.print(str1 == str6); // false - System.out.println(); - - System.out.println("---------intern测试结果---------"); // intern 直奔常量池上找 - System.out.print("str1.intern() == str2.intern() ? "); - System.out.println(str1.intern() == str2.intern()); // true - System.out.print("str1.intern() == str5.intern() ? "); - System.out.println(str1.intern() == str5.intern()); // true - System.out.print("str1.intern() == str6.intern() ? "); - System.out.println(str1.intern() == str6.intern()); // true - System.out.print("str1 == str6.intern() ? "); - System.out.println(str1 == str6.intern()); // true - } - -``` -结果一目了然, 但需要知道原理: -``` -------普通String测试结果------ -str1 == str2 ? true -str1 == str5 ? false -str1 == str6 ? false ----------intern测试结果--------- -str1.intern() == str2.intern() ? true -str1.intern() == str5.intern() ? true -str1.intern() == str6.intern() ? true -str1 == str6.intern() ? true -``` - -待我分析一波: -> jvm你可要知道哇。 -> Java语言会使用常量池保存那些在编译期就已确定的已编译的class文件中的一份数据。主要有类、接口、方法中的常量,以及一些以文本形式出现的符号引用,如类和接口的全限定名、字段的名称和描述符、方法和名称和描述符等。 - -- 因此在编译完Intern类后,生成的class文件中会在常量池中保存“todo”、“to”和“do”三个String常量。 -- 变量str1和str2均保存的是常量池中“todo”的引用,所以str1==str2成立; -- 在执行 str5 = str3 + str4这句时,JVM会先创建一个StringBuilder对象,通过StringBuilder.append()方法将str3与str4的值拼接,然后通过StringBuilder.toString()返回一个堆中的String对象的引用,赋值给str5,因此str1和str5指向的不是同一个String对象,str1 == str5不成立; -- String str6 = new String(str1)一句显式创建了一个新的String对象,因此str1 == str6不成立便是显而易见的事了。 - -重要的一点就是intern了: - -> String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现。当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String, - -- 若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。 -- 因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用,所以,这些等值的String对象通过intern()后使用==是可以匹配的。 -- 由此就可以理解上面代码中------intern------部分的结果了。因为str1、str5和str6是三个等值的String,所以通过intern()方法,**他们均会指向常量池中的同一个String引用**,因此str1.intern() == str5.intern() == str6.intern()均为true。 - -额外补充一哈: - -#### JDK6 -> Jdk6中常量池位于PermGen(永久代)中,PermGen是一块主要用于存放已加载的类信息和字符串池的大小固定的区域。执行intern()方法时,**若常量池中不存在等值的字符串,JVM就会在常量池中创建一个等值的字符串,然后返回该字符串的引用**。除此以外,JVM 会自动在常量池中保存一份之前已使用过的字符串集合。Jdk6中使用intern()方法的主要问题就在于常量池被保存在PermGen中: -> 首先,PermGen是一块大小固定的区域,一般不同的平台PermGen的默认大小也不相同,大致在32M到96M之间。所以不能对不受控制的运行时字符串(如用户输入信息等)使用intern()方法,否则很有可能会引发PermGen内存溢出;其次String对象保存在Java堆区,Java堆区与PermGen是物理隔离的,因此如果对多个不等值的字符串对象执行intern操作,则会导致内存中存在许多重复的字符串,会造成性能损失。 - -#### JDK7 -> Jdk7将常量池从PermGen区移到了Java堆区,执行intern操作时,如果常量池已经存在该字符串,则直接返回字符串引用,否则**复制该字符串对象的引用到常量池中并返回**。堆区的大小一般不受限,所以将常量池从PremGen区移到堆区使得常量池的使用不再受限于固定大小。 -> 除此之外,位于堆区的常量池中的对象可以被垃圾回收。当常量池中的字符串不再存在指向它的引用时,JVM就会回收该字符串。可以使用 -XX:StringTableSize 虚拟机参数设置字符串池的map大小。字符串池内部实现为一个HashMap,所以当能够确定程序中需要intern的字符串数目时,可以将该map的size设置为所需数目*2(减少hash冲突),这样就可以使得String.intern()每次都只需要常量时间和相当小的内存就能够将一个String存入字符串池中。 - - -#### 适应场景 -Jdk6中常量池位于PermGen区,大小受限,所以不建议适用intern()方法,当需要字符串池时,需要自己使用HashMap实现。Jdk7、8中,常量池由PermGen区移到了堆区,还可以通过-XX:StringTableSize参数设置StringTable的大小,常量池的使用不再受限,由此可以重新考虑使用intern()方法。intern()方法优点:执行速度非常快,直接使用==进行比较要比使用equals()方法快很多;内存占用少。虽然intern()方法的优点看上去很诱人,但若不是在恰当的场合中使用该方法的话,便非但不能获得如此好处,反而还可能会有性能损失。 diff --git "a/Interview/codes/basics/equals\345\222\214hashcode.md" "b/Interview/codes/basics/equals\345\222\214hashcode.md" deleted file mode 100644 index aee59082..00000000 --- "a/Interview/codes/basics/equals\345\222\214hashcode.md" +++ /dev/null @@ -1,115 +0,0 @@ -## equals和hashcode - -### == -它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象: -- 基本数据类型==比较的是值 -- 引用数据类型==比较的是内存地址 - -### equals -它的作用也是判断两个对象是否相等。但它一般有两种使用情况: -- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过`==`比较这两个对象。 -- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 - -```java -public class test1 { - public static void main(String[] args) { - String a = new String("ab"); // a 为一个引用 - String b = new String("ab"); // b为另一个引用,对象的内容一样 - String aa = "ab"; // 放在常量池中 - String bb = "ab"; // 从常量池中查找 - if (aa == bb) // true - System.out.println("aa==bb"); - if (a == b) // false,非同一对象 - System.out.println("a==b"); - if (a.equals(b)) // true - System.out.println("aEQb"); - if (42 == 42.0) { // true - System.out.println("true"); - } - } -} -``` - -注意:这里String类重写了equals方法。 -- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。 -- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 - -```java -public boolean equals(Object anObject) { - if (this == anObject) { // 比较两个对象的地址 - return true; - } - if (anObject instanceof String) { // 地址不相同,开始比较内容 - String anotherString = (String)anObject; - int n = value.length; - if (n == anotherString.value.length) { - char v1[] = value; - char v2[] = anotherString.value; - int i = 0; - while (n-- != 0) { - if (v1[i] != v2[i]) - return false; - i++; - } - return true; - } - } - return false; - } -``` - -### hashcode -> hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。 -> 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) - -#### 比如 -> 我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置 -> 这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - -```java -public class Demo { - public static void main(String[] args) { - HashSet set = new HashSet<>(); - set.add("1"); - set.add("2"); - set.add("1"); - System.out.println(set.toString()); - } -} -``` -可以看一下HashSet源码中的add,实际上是HashMap的put方法。 - -```java -public boolean add(E e) { - return map.put(e, PRESENT)==null; -} -``` - -接着看HashMap的put方法 -```java -public V put(K key, V value) { - return putVal(hash(key), key, value, false, true); -} -``` -其中,hash的这个方法的源码,你可看 -```java -static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); -} -``` -也就是你传入这个键的对象的hashcode,本例是String,可以看:也是重写了hashcode的。所以,像使用集合,判断对象是否相等,还是务必重写hashcode和equals。put具体细节源码就不贴了 -```java -public int hashCode() { - int h = hash; - if (h == 0 && value.length > 0) { - char val[] = value; - - for (int i = 0; i < value.length; i++) { - h = 31 * h + val[i]; - } - hash = h; - } - return h; -} -``` \ No newline at end of file diff --git "a/Interview/codes/basics/\345\217\215\345\260\204\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/basics/\345\217\215\345\260\204\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index ed29af72..00000000 --- "a/Interview/codes/basics/\345\217\215\345\260\204\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,208 +0,0 @@ -# 反射 -> Java反射机制是在**运行状态**中,对于任意一个类,都能够知道**这个类的所有属性和方法**;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动**态获取的信息以及动态调用对象的方法的功能**成为java语言的反射机制。 - -## 静态编译和动态编译 -- 静态编译:在编译时确定类型,绑定对象 -- 动态编译:运行时确定类型,绑定对象 - -## 反射机制优缺点 -- 优点:运行期间类型的判断,动态加载类,提高代码的灵活度 -- 缺点:性能瓶颈:反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多 - -## 反射的应用场景 -> 在我们平时的项目开发过程中,基本上很少会直接使用的反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如**模块化的开发**,通过反射去调用对应的字节码;**动态代理设计模型也采用了反射机制**,还有我们日常使用的`Spring` / `Hibernate`等框架也大量使用到了反射机制。 - -## 反射得到的Class对象的三种方式 -首先是Student类 -```java -public class Student { - - private String name; - - private Integer age; - - public String gender; - - // 无参数的构造方法 - public Student() { - System.out.println("调用了公有、无参构造方法执行了。。。"); - } - - // 默认的构造方法 - public Student(String str) { - System.out.println("(默认)的构造方法 s = " + str); - } - - // 有一个参数的构造方法 - public Student(char name) { - System.out.println("姓名:" + name); - } - - // 有多个参数的构造方法 - public Student(String name, Integer age) { - this.name = name; - this.age = age; - } - - //受保护的构造方法 - protected Student(boolean n){ - System.out.println("受保护的构造方法 n = " + n); - } - - //私有构造方法 - private Student(int age){ - System.out.println("私有的构造方法 年龄:"+ age); - } - - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getAge() { - return age; - } - - public void setAge(Integer age) { - this.age = age; - } - - public String getGender() { - return gender; - } - - public void setGender(String gender) { - this.gender = gender; - } - - //**************成员方法***************// - public void show1(String s){ - System.out.println("调用了:公有的,String参数的show1(): s = " + s); - } - protected void show2(){ - System.out.println("调用了:受保护的,无参的show2()"); - } - void show3(){ - System.out.println("调用了:默认的,无参的show3()"); - } - private String show4(int age){ - System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age); - return "abcd"; - } - - @Override - public String toString() { - return "Student{" + - "name='" + name + '\'' + - ", age=" + age + - ", gender='" + gender + '\'' + - '}'; - } -} -``` -测试: -```java -public class ReflectDemo { - public static void main(String[] args) throws ClassNotFoundException { - // 第一种方式获取Class对象 - Student student = new Student(); // 这一new 产生一个Student对象,一个Class对象。 - Class studentClass = student.getClass(); - System.out.println(studentClass.getName()); // com.reflect.Student - - // 第二种方式获取Class对象 - Class studentClass2 = Student.class; - System.out.println(studentClass == studentClass2); //判断第一种方式获取的Class对象和第二种方式获取的是否是同一个 - - //第三种方式获取Class对象 - Class studentClass3 = Class.forName("com.reflect.Student"); //注意此字符串必须是真实路径,就是带包名的类路径,包名.类名 - System.out.println(studentClass3 == studentClass2); // //判断三种方式是否获取的是同一个Class对象 - - // 三种方式常用第三种,第一种对象都有了还要反射干什么。 - // 第二种需要导入类的包,依赖太强,不导包就抛编译错误。 - // 一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。 - } -} -``` -## 反射访问并调用构造方法 -```java -public class ConstructorsDemo { - public static void main(String[] args) throws Exception { - // 1. 加载Class对象 - Class clazz = Class.forName("com.reflect.Student"); - - // 2. 获取所有公有构造方法 - System.out.println("**********************所有公有构造方法*********************************"); - Constructor[] constructors = clazz.getConstructors(); - for (Constructor constructor : constructors) { - System.out.println(constructor); - } - - // 3. - System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************"); - Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); - for (Constructor declaredConstructor : declaredConstructors) { - System.out.println(declaredConstructor); - } - - // 4. - System.out.println("*****************获取公有、无参的构造方法*******************************"); - Constructor constructor = clazz.getConstructor(); - System.out.println(constructor); - - // 调用构造方法 - Object object = constructor.newInstance(); - System.out.println(object); - - // - System.out.println("******************获取私有构造方法,并调用*******************************"); - Constructor constructor1 = clazz.getDeclaredConstructor(char.class); - System.out.println(constructor1); - // 调用构造方法 - constructor1.setAccessible(true); // 暴力访问 - Object object2 = constructor1.newInstance('买'); - System.out.println(object2); - } -} -``` - -## 反射访问并调用成员变量 -```java -public class FieldDemo { - public static void main(String[] args) throws Exception { - // 1. 获取class对象 - Class clazz = Class.forName("com.reflect.Student"); - // 2. 获取所有字段 - System.out.println("************获取所有公有的字段********************"); - Field[] fields = clazz.getFields(); - for (Field field : fields) { - System.out.println(field); - } - // - System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************"); - Field[] fields1 = clazz.getDeclaredFields(); - for (Field field : fields1) { - System.out.println(field); - } - // - System.out.println("*************获取公有字段**并调用***********************************"); - Field gender = clazz.getField("gender"); - System.out.println(gender); - // 获取一个对象 - Object o = clazz.getConstructor().newInstance(); - gender.set(o, "男"); - Student stu = (Student) o; - System.out.println("验证性别:" + stu.getGender()); - // - System.out.println("*************获取公有字段**并调用***********************************"); - Field name = clazz.getDeclaredField("name"); - System.out.println(name); - name.setAccessible(true); //暴力反射,解除私有限定 - name.set(o, "买"); - System.out.println("验证姓名:" + stu); - } -} -``` diff --git "a/Interview/codes/basics/\345\270\270\350\247\201\345\205\263\351\224\256\345\255\227\351\227\256\351\242\230.md" "b/Interview/codes/basics/\345\270\270\350\247\201\345\205\263\351\224\256\345\255\227\351\227\256\351\242\230.md" deleted file mode 100644 index 7e061e4b..00000000 --- "a/Interview/codes/basics/\345\270\270\350\247\201\345\205\263\351\224\256\345\255\227\351\227\256\351\242\230.md" +++ /dev/null @@ -1,18 +0,0 @@ -## 常见的关键字问题 -### final - -> final关键字主要用在三个地方:变量、方法、类。 - -1. **对于一个final变量**,如果是基本数据类型的变量,则其**数值一旦在初始化之后便不能更改**;如果是引用类型的变量,则在对其**初始化之后便不能再让其指向另一个对象**。 -2. **使用final方法的原因**有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。 -3. **当用final修饰一个类时**,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 - -好处: -1. final的关键字提高了性能,JVM和java应用会缓存final变量; -2. final变量可以在多线程环境下保持线程安全; -3. 使用final的关键字提高了性能,JVM会对方法变量类进行优化; - -### static -1. **修饰成员变量和成员方法**: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名() -2. **静态代码块**: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块**只执行一次**. -3. **静态内部类**(static修饰类的话只能修饰内部类):静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 diff --git "a/Interview/codes/basics/\345\274\202\345\270\270\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/basics/\345\274\202\345\270\270\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 7f0d1583..00000000 --- "a/Interview/codes/basics/\345\274\202\345\270\270\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,67 +0,0 @@ -# 异常的一些问题 -> 在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 Throwable类。 -- **Exception(异常)** -- **Error(错误)** - -## 思维导图 -![Throwable](http://media.dreamcat.ink/uPic/%E5%BC%82%E5%B8%B8%E5%88%86%E7%B1%BB.png) - -## Error -**是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。比如:`OutOfMemoryError` - -## Exception -**是程序本身可以处理的异常。** 比如: - -- `RuntimeException` 异常由Java虚拟机抛出。 -- `NullPointerException`(要访问的变量没有引用任何对象时,抛出该异常) -- `ArithmeticException`(算术运算异常,一个整数除以0时,抛出该异常) -- `ArrayIndexOutOfBoundsException` (下标越界异常)。 - -## 处理 - -- `try` 块: 用于捕获异常。其后可接零个或多个`catch`块,如果没有`catch`块,则必须跟一个`finally`块。 -- `catch` 块: 用于处理`try`捕获到的异常。 -- `finally` 块: 无论是否捕获或处理异常,`finally`块里的语句都会被执行。当在`try`块或`catch`块中遇到`return`语句时,`finally`语句块将在方法返回之前被执行。 - -> 注意:当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会**覆盖原始的返回值**。如下: - -```java - public static int f(int value) { - try { - return value * value; - } finally { - if (value == 2) { - return 0; - } - } - } -``` - -## throw和throws -> 抛出异常 - -throw的例子: - -```java -public class Test1 { - public static void main(String[] args) { - if(true) { - throw new NumberFormatException(); // 抛出一个异常 - } else { - ... - } - } -} -``` - -throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常),throws例子: - -```java -public class Test2 { - public void fun() throws NumberFormatException { - if(true) { - throw new NumberFormatException(); - } - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/basics/\346\265\205\346\213\267\350\264\235\345\222\214\346\267\261\346\213\267\350\264\235\344\276\213\345\255\220.md" "b/Interview/codes/basics/\346\265\205\346\213\267\350\264\235\345\222\214\346\267\261\346\213\267\350\264\235\344\276\213\345\255\220.md" deleted file mode 100644 index 1f4f2078..00000000 --- "a/Interview/codes/basics/\346\265\205\346\213\267\350\264\235\345\222\214\346\267\261\346\213\267\350\264\235\344\276\213\345\255\220.md" +++ /dev/null @@ -1,145 +0,0 @@ -## 深拷贝和浅拷贝 - -> 1. **浅拷贝**:对**基本数据类型进行值传递**,对**引用数据类型进行引用传递般的拷贝**,此为浅拷贝。 -> 2. **深拷贝**:对**基本数据类型进行值传递**,对**引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝** -> 3. 也就二者对引用数据类型有区别 - -### 举例子 -1. 首先看浅拷贝: - -Subject类: -```java -public class Subject { - - private String name; - - public Subject(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Subject{" + - "name='" + name + '\'' + - '}'; - } -} -``` - -Student类: -```java -public class Student implements Cloneable{ - - // 对象的引用 - private Subject subject; - - private String name; - - public Student(Subject s, String name) { - this.subject = s; - this.name = name; - } - - public Subject getSubject() { - return subject; - } - - public String getName() { - return name; - } - - public void setSubject(Subject subject) { - this.subject = subject; - } - public void setName(String name) { - this.name = name; - } - - @Override - public String toString() { - return "Student{" + - "subject=" + subject + - ", name='" + name + '\'' + - '}'; - } - - @Override - protected Object clone() throws CloneNotSupportedException { - // 浅拷贝 -// return super.clone(); - } -} -``` -测试: -```java -public class ShallowCopyDemo { - public static void main(String[] args) throws CloneNotSupportedException { - // 原始对象 - Student student = new Student(new Subject("code"), "dream"); - System.out.println("原始对象: " + student.getName() + " - " + student.getSubject().getName()); // dream-code - - // 拷贝对象 - Student cloneStu = (Student) student.clone(); - System.out.println("拷贝对象: " + cloneStu.getName() + " - " + cloneStu.getSubject().getName()); // dream-code - - // 原始对象和拷贝对象是否一样: - System.out.println("原始对象和拷贝对象是否一样: " + (student == cloneStu)); // false - - // 原始对象和拷贝对象的name属性是否一样 - System.out.println("原始对象和拷贝对象的name属性是否一样: " + (student.getName() == cloneStu.getName())); // true - - // 原始对象和拷贝对象的subj属性是否一样 - System.out.println("原始对象和拷贝对象的subj属性是否一样: " + (student.getSubject() == cloneStu.getSubject())); // true - - student.setName("cat"); - student.getSubject().setName("eat"); - System.out.println("更新后的原始对象: " + student.getName() + " - " + student.getSubject().getName()); // cat-eat - System.out.println("更新原始对象后的克隆对象: " + cloneStu.getName() + " - " + cloneStu.getSubject().getName()); // dream-eat - // 在这个例子中,让要拷贝的类Student实现了Clonable接口并重写Object类的clone()方法,然后在方法内部调用super.clone()方法。 - // 从输出结果中我们可以看到,对原始对象stud的"name"属性所做的改变并没有影响到拷贝对象clonedStud; - // 但是对引用对象subj的"name"属性所做的改变影响到了拷贝对象clonedStud。 - } -} -``` -运行结果: -```shell -原始对象: dream - code -拷贝对象: dream - code -原始对象和拷贝对象是否一样: false -原始对象和拷贝对象的name属性是否一样: true -原始对象和拷贝对象的subj属性是否一样: true -更新后的原始对象: cat - eat -更新原始对象后的克隆对象: dream - eat -``` - -2. 深拷贝 - -只需要重写Student的clone方法 -```java - @Override - protected Object clone() throws CloneNotSupportedException { - Student student = new Student(new Subject(subject.getName()), name); // 新建对象,拷贝内容 - return student; - // 因为它是深拷贝,所以你需要创建拷贝类的一个对象。 - // 因为在Student类中有对象引用,所以需要在Student类中实现Cloneable接口并且重写clone方法。 - } -``` -测试结果: -```shell -原始对象: dream - code -拷贝对象: dream - code -原始对象和拷贝对象是否一样: false -原始对象和拷贝对象的name属性是否一样: true -原始对象和拷贝对象的subj属性是否一样: false -更新后的原始对象: cat - eat -更新原始对象后的克隆对象: dream - code -``` -晓得了吧? \ No newline at end of file diff --git "a/Interview/codes/basics/\347\261\273\345\236\213\350\275\254\346\215\242\347\232\204\351\227\256\351\242\230.md" "b/Interview/codes/basics/\347\261\273\345\236\213\350\275\254\346\215\242\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index 39423d20..00000000 --- "a/Interview/codes/basics/\347\261\273\345\236\213\350\275\254\346\215\242\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,94 +0,0 @@ -## 类型转换的问题 - - -### 比如 - -```java -short s1 = 1; -s1 = s1 + 1;有什么错? -short s1 = 1; -s1 += 1; 有什么错? -``` - - -- 对于 `short s1 = 1; s1 = s1 + 1;` 由于 s1+1 运算时会自动提升表达式的类型,所以结果是 int 型,再赋值给 short 类型 s1 时,编译器将报告需要强制转换类型的错误。 -- 对于 `short s1 = 1; s1 += 1;`由于 += 是 **java 语言规定的运算符**,java 编译器会对它进行特殊处理,因此 可以正确编译。 - -```java -public class TypeConvert { - public static void main(String[] args) { - // 字面量属于 double 类型 - // 不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型 - // Java 不能隐式执行向下转型,因为这会使得精度降低。 - // float f = 1.1; - float f = 1.1f; - - // 因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。 - short s1 = 1; - // s1 = s1 + 1; - - // 但是使用 += 运算符可以执行隐式类型转换。 - s1 += 1; - // 上面的语句相当于将 s1 + 1 的计算结果进行了向下转型 如下: - s1 = (short) (s1 + 1); - - } -} - -``` - -### 聊聊整形包装类的缓存池 -举个例子: -```java -public class IntegerPackDemo { - public static void main(String[] args) { - Integer x = 3; // 装箱,3自动包装为Integer - int z = x; // 拆箱 x 拆箱为基本类型 - Integer y = 3; - System.out.println(x == y); // true 基本类型比较可用== - // ------------------------- - Integer a = new Integer(3); - Integer b = new Integer(3); - System.out.println(a == b); // false 老生常谈了,就不说为什么了 - System.out.println(a.equals.(b)); // true // 这里是用重写了equals方法,比较的是值,而不是对象的地址 - // ------------------------ - // 缓存池 - Integer aa = Integer.valueOf(123); - Integer bb = Integer.valueOf(123); - System.out.println(aa == bb); // true - /** - * valueOf的源码 - * public static Integer valueOf(int i) { - * // 判断是否在Integer的范围内 - * if (i >= IntegerCache.low && i <= IntegerCache.high) - * return IntegerCache.cache[i + (-IntegerCache.low)]; - * return new Integer(i); - * } - */ - } -} - -``` -个人还是喜欢贴Integer的equals源码: -```java -public boolean equals(Object obj) { - if (obj instanceof Integer) { - return value == ((Integer)obj).intValue(); // 这里比的就是值了 - } - return false; -} -``` -继续看valueOf方法的源码: - -```java -// 源码描述: -// This method will always cache values in the range -128 to 127, -public static Integer valueOf(int i) { - if (i >= IntegerCache.low && i <= IntegerCache.high) // 条件 - return IntegerCache.cache[i + (-IntegerCache.low)];// 取缓存, - // Integeer的源码中: - // static final int low = -128; IntegerCache.low = -128 - return new Integer(i); -} -``` -> 当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。 diff --git "a/Interview/codes/basics/\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/basics/\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a42fb42c..00000000 --- "a/Interview/codes/basics/\351\235\242\345\220\221\345\257\271\350\261\241\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,141 +0,0 @@ -## 三大特性 -### 封装 -> 封装把一个对象的**属性私有化**,同时提供一些可以**被外界访问的属性的方法**,**如果属性不想被外界访问,我们大可不必提供方法给外界访问**。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 - -代码举个例子也妥: -```java -class Person{ - private String name; // 属性私有,并不想暴漏 - private Integer age; // 属性私有 - - // 给外界一个访问方法,给name赋值 - public void setName(String name) { - this.name = name; - } - - // 给外界一个访问方法,获取name的值 - public String getName() { - return this.name; - } -} -``` - -### 继承 -> 继承是使用**已存在的类**的定义作为基础建立新类的技术,新类的定义可以增加**新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类**。通过使用继承我们能够非常方便地复用以前的代码。 - -代码举个例子撒: -```java -import java.util.*; - -public class Person { - private String name; - - private Integer age; - - public void setName(String name) { - this.name = name; - } - - public void setAge(Integer age) { - this.age = age; - } - - public String getName() { - return this.name; - } - - public Integer getAge() { - return this.age; - } - - public static void main(String[] args) { - Student s = new Student(); - s.setName("Dream"); - s.setAge(18); - s.setSchool("家里蹲"); - s.say(); - } -} - -class Student extends Person{ // 集成父类 - private String school; - - public void setSchool(String school) { - this.school = school; - } - - public String getSchool() { - return this.school; - } - - public void say(){ - System.out.println(this.getName() + " say: 我tm" + this.getAge() + "岁,不服?不服就来" + this.getSchool()); - } -} - -``` - -注意: -- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。 -- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 -- 子类可以用自己的方式实现父类的方法。(以后介绍)。 - -### 多态 -> 所谓多态就是指程序中定义的**引用变量**所指向的**具体类型**和通过**该引用变量发出的方法调用**在编程时并不确定,而是在**程序运行期间才确定**,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 -> 说白了,就是任何事物的多个姿态,多个形态。比如,你说一个猫在吃东西,同样的,你也能说一个动物在吃东西。 - -一般两种方式: -- 继承(多个子类对同一方法的重写) -- 接口(实现接口并覆盖接口中同一方法) - -举个例子吧: -定义一个动物类 -```java -class Animal { - public void eat() { - System.out.println("动物在吃东西..."); - } -} -``` - -定义一个猫 -```java -class Cat extends Animal{ - @Override - public void eat() { - System.out.println("猫在吃东西..."); - } -} -``` -测试 -```java -public class Test { - public static void main(String[] args){ - Animal animal = new Cat(); - animal.eat() // 猫也会吃饭 - // 你看到了一只猫,同样它也是动物 - // 比如有很多其他种类继承了动物哈, - // 当编译期间的animal引用变量,到底指的哪个实例对象, - // 或者该引用调用的eat方法,到底是哪个实例对象的eat,编译期间恐怕不知道哦 - // 只有运行期间,哦哦, 原来是猫的eat方法哇... - } -} -``` - -### 重载和重写的区别 - -- 重载 -> 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 -> 例子的话,你看源码的时候,那里面的方法,你晓得伐? - -- 重写 -> 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。也就是说方法提供的行为改变,而方法的外貌并没有改变。 -> 当你看源码出现继承的时候, 你晓得伐? - -### 接口和抽象类的区别是什么? -1. 方法:接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。 -2. 变量:接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。 -3. 实现:一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过implement关键字扩展多个接口。 -4. 修饰符:接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 - - diff --git "a/Interview/codes/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" "b/Interview/codes/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" deleted file mode 100644 index 4e2167ac..00000000 --- "a/Interview/codes/bus/Redis\347\232\204key\350\277\207\346\234\237\344\272\213\344\273\266.md" +++ /dev/null @@ -1,68 +0,0 @@ -# 订单自动取消 - -> 如果系统的并发量不是特别高的,或者日访问量不是特别高的话,可以使用Redis的key过期事件,如果访问量过高,还是采用延迟队列吧。 - -## Redis的配置 -``` -# -# notify-keyspace-events Ex -# -# By default all notifications are disabled because most users don't need -# this feature and the feature has some overhead. Note that if you don't -# specify at least one of K or E, no events will be delivered. -notify-keyspace-events "Ex" -``` - -## RedisListenerConfig配置 -```java -@Configuration -public class RedisListenerConfig { - @Bean - RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) { - RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(connectionFactory); - return container; - } -} -``` - -## RedisKeyExpirationListener监听事件 - -```java -@Component -@Slf4j -public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { - public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { - super(listenerContainer); - } - - @Autowired - private IOrderService orderService; - - /** - * 监听key过期事件 - * @param message - * @param pattern - */ - @Override - public void onMessage(Message message, byte[] pattern) { - super.onMessage(message, pattern); - String expiredKey = message.toString(); - log.info("redis key过期:{}",expiredKey); - String orderCancleKey = RedisConstants.ORDER_CANCLE_EXPIRE.getKey(); - if (expiredKey.startsWith(orderCancleKey)) { - // 获取订单id - String[] strings = expiredKey.split(orderCancleKey); - String orderId = strings[1]; - log.warn("过期订单ID:" + orderId); - // 得到过期订单。 - // 1。 更改订单状态就完事了 - OrderRequest request = new OrderRequest(); - request.setOrderStatus("2"); // 关闭订单 - request.setUuid(Convert.toLong(orderId)); - orderService.updateOrderStatus(request); - log.warn("过期订单已处理:" + orderId); - } - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/bus/Redis\347\273\221\345\256\232Token.md" "b/Interview/codes/bus/Redis\347\273\221\345\256\232Token.md" deleted file mode 100644 index 550fbd30..00000000 --- "a/Interview/codes/bus/Redis\347\273\221\345\256\232Token.md" +++ /dev/null @@ -1,201 +0,0 @@ -# Redis绑定Token -> 先介绍一下概念 - -## cookie -HTTP 协议是**无状态的**,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 - -Cookie 是服务器发送到用户浏览器并保存在**本地的一小块数据**,它会在浏览器之后向同一服务器**再次发起请求时被携带上**,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 - -### 用途 -- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) -- 个性化设置(如用户自定义设置、主题等) -- 浏览器行为跟踪(如跟踪分析用户行为等) - -## session -除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 - -Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 - -使用 Session 维护用户登录状态的过程如下: -1. 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; -2. 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; -3. 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; -4. 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 - -> 注意:Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 - -### session和cookie选择 -- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; -- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; -- 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 - -## Token -实际上,Token是在服务端将用户信息经过Base64Url编码过后传给在客户端。每次用户请求的时候都会带上这一段信息,因此服务端拿到此信息进行解密后就知道此用户是谁了,这个方法叫做JWT(Json Web Token)。 - -## JWT -> JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。 - -cookie+session这种模式通常是保存在内存中,而且服务从**单服务到多服务会面临的session共享问题**,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。 - -### 构成 -第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。详情请见[官网](https://jwt.io/introduction/) - -简单说一下 - -#### header -是一个Json对象,描述JWT的元数据,通常是下面这样子的: -```json -{ - "alg": "HS256", - "typ": "JWT" -} -``` -上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。最后,将上面的 JSON 对象使用 Base64URL 算法转成字符串。 - - -#### payload -Payload部分也是一个Json对象,用来存放实际需要传输的数据,JWT官方规定了下面几个官方的字段供选用。 - -- iss (issuer):签发人 -- exp (expiration time):过期时间 -- sub (subject):主题 -- aud (audience):受众 -- nbf (Not Before):生效时间 -- iat (Issued At):签发时间 -- jti (JWT ID):编号 - -也可以定义私有属性 - -#### signature -Signature部分是对前面的两部分的数据进行签名,防止数据篡改。 - -首先需要定义一个秘钥,这个秘钥只有服务器才知道,不能泄露给用户,然后使用Header中指定的签名算法(默认情况是HMAC SHA256),算出签名以后将Header、Payload、Signature三部分拼成一个字符串,每个部分用.分割开来,就可以返给用户了。 - -## 在项目中如何写 - -### JwtTokenUtil -这里是使用guns搭建的项目,自带的工具类,我就不详细赘述了,主要是一下我们的业务逻辑,以及如何使用。 - - -### AuthController -> sb-gateway-auth-AuthController - -能请求到AuthContoroller,说明两种情况: -1. 你没有携带token -2. 你token过期了 - -而这个业务处理,是为了生成token - -```java - @ApiOperation(value = "获取token接口", notes = "每调用一次,就会随机生成一串token", response = ResponseData.class) - @RequestMapping(value = "${jwt.auth-path}") - public ResponseData createAuthenticationToken(@Validated AuthRequest authRequest, BindingResult bindingResult) { - //Validated 的作用是抵消了使用很多if判断 - if (bindingResult.hasErrors()) { - for (FieldError fieldError : bindingResult.getFieldErrors()) { - log.error("参数:{}校验失败,原因:{}", fieldError.getField(), fieldError.getDefaultMessage()); - } - return new ResponseUtil<>().setErrorMsg("用户参数设置错误:" + CommonBindingResult.getErrors(bindingResult)); - } - // 获取用户的账号和密码 - UserLoginRequst req = new UserLoginRequst(); - req.setUsername(authRequest.getUserName()); - req.setPassword(authRequest.getPassword()); - // 调用userAPI.login业务获取用户id - UserLoginResponse res = userAPI.login(req); - String userId = "" + res.getUserId(); - if (res.getUserId() != 0) { - // 如果id不等于0,说明账户存 - // 从redis看一下userId在不在 - if (redisUtils.hasKey(userId)) { - // 如果存在,两种情况:1.你在其他登陆,2.别人登陆 - // 删除redis中的键为userId的 - redisUtils.del(userId); - } - // 以下两步,针对于userId生成token - res.setRandomKey(jwtTokenUtil.getRandomKey()); - String token = jwtTokenUtil.generateToken(userId, res.getRandomKey()); - res.setToken(token); - // 写进redis - redisUtils.set(userId, token, RedisConstants.TOKEN_EXPIRE.getTime()); - // 返回给用户 - return new ResponseUtil<>().setData(res); - } else { - // 如果id等于0,那么说明两种情况:1.账号密码错误,2.账户不存在 - return new ResponseUtil<>().setErrorMsg("账号密码错误"); - } - } -``` - -### AuthFilter -> sb-gateway->auth->filter->AuthFileter - -#### 忽略列表 -```java -// 配置忽略列表 -String ignoreUrl = jwtProperties.getIgnoreUrl(); // 获取忽略的url -String[] ignoreUrls = ignoreUrl.split(","); -for(int i=0;i().setData(response1)); - return; - } else { - // 如果存在,去取token - String token = (String) redisUtils.get(userId); - // 判断redis的token和当前的浏览器的token是否一致 - if(!token.equals(authToken)) { - // 如果不相等,说明别人已经登录了,你当前的token无效,需要重新登录,将它替换掉。 - // 比如,两个手机不能同时登录同一个账户的QQ - CommonResponse response1 = new CommonResponse(); - response1.setCode(SbCode.TOKEN_VALID_FAILED.getCode()); - response1.setMsg(SbCode.TOKEN_VALID_FAILED.getMessage()); - RenderUtil.renderJson(response, new ResponseUtil<>().setData(response1)); - return; - } - } - } catch (Exception e) { - // 如果发生异常,所携带的token有问题,防止爬虫造token - CommonResponse response1 = new CommonResponse(); - response1.setCode(SbCode.TOKEN_VALID_FAILED.getCode()); - response1.setMsg(SbCode.TOKEN_VALID_FAILED.getMessage()); - RenderUtil.renderJson(response, new ResponseUtil<>().setData(response1)); - return; - } - } else { - //header没有带Bearer字段 - CommonResponse response1 = new CommonResponse(); - response1.setCode(SbCode.TOKEN_VALID_FAILED.getCode()); - response1.setMsg(SbCode.TOKEN_VALID_FAILED.getMessage()); - RenderUtil.renderJson(response, new ResponseUtil<>().setData(response1)); - return; - } - chain.doFilter(request, response); - } -``` - -**以上流程图,日后补充** \ No newline at end of file diff --git "a/Interview/codes/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" "b/Interview/codes/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" deleted file mode 100644 index dd379ca9..00000000 --- "a/Interview/codes/bus/\344\270\212\347\272\277\351\201\207\345\210\260\347\232\204bug.md" +++ /dev/null @@ -1,200 +0,0 @@ -## 上线bug - -> 项目上线之后,大概将近一个月,我点击车次列表页面,突然什么都没有了,于是就开始找哪个不顺眼的家伙搞的鬼。 - -### 起因 - -访问主页[http://47.104.22.225:8080/home](http://47.104.22.225:8080/home) - -![](https://imgkr.cn-bj.ufileos.com/8c563c2e-49f8-4a96-8d02-0109bc956449.png) - -如图: - -一直加载中,于是我看一下响应信息。 - -```json -code: 200 -message: "success" -result: {code: "003099", msg: "系统错误"} -success: true -timestamp: "1589715142518" -``` - -### 查看一下gateway的日志 -- 终端显示不长,于是我将日志用vscode打开看 - -```log -org.apache.dubbo.rpc.RpcException: No provider available from registry 39.108.93.119:2181 for service com.stylefeng.guns.rest.bus.IBusService on consumer 192.168.31.221 use dubbo version 2.7.4.1, please check status of providers(disabled, not registered or in blacklist). -``` - -类似于这样的信息,说我们的bus服务没有注册。 - -> 注意:出事之前,几个服务都在的呀,怎么今天bus突然不在了。于是,我不相信,我就去dubbo后台看了一下... - -- 进入文件夹`dubbo-admin -> dubbo-admin-ui` -- 执行`npm run dev` -- 在我目前的mac上chrome浏览器输入`http://dubbo.dreamcat.ink:2020/` - -**此时,还真没有bus服务** -![](https://imgkr.cn-bj.ufileos.com/3eb38910-64b5-439c-8437-c155d50857d2.png) - -**哭晕在厕所**。 - -![](https://imgkr.cn-bj.ufileos.com/e391627b-dce7-4146-b6d7-78cbd1fee35e.jpg) - -**于是乎,俺又不想重新启动服务,毕竟你看** - -- 终端输入`ps -ef | grep guns-bus` -- 我们看到了惊人的一幕 -```shell -pch 2003942 1 0 4月16 ? 06:10:54 java -jar guns-bus-0.0.1.jar -``` - -**又哭晕在厕所...** - -### 猜测 - -#### DubboAdmin展示? -好像没问题吧?这样的话,其他三个也应该不存在的啊 - -#### 注册中心,bus节点丢了 - -- 查看日志,当天出现zk出现了大量的超时,原因是当天的zk**主节点**宕机了。 - -#### 找原因 - -**问题是否出现在了dubbo对zk重连恢复数据这块,开始查源码。注册中心源码ZookeeperRegistry。** - -1. 连接注册zk:通过zkclient添加zk状态监听。并且继承了FailbackRegistry各种失败重试。 - -```java -public class ZookeeperRegistry extends FailbackRegistry { - public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) { - ... - // 1. 连接zk - zkClient = zookeeperTransporter.connect(url); - // 2. 添加zk状态监听 - zkClient.addStateListener(new StateListener() { - @Override - public void stateChanged(int state) { - // 3. 重新连接后恢复动作,将当前的注册服务于订阅任务添加至重试列表中等待重试 - if (state == RECONNECTED) { - try { - recover(); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - } - }); - } -} - -``` - -2. zk客户端:默认使用CuratorZookeeperClient实现 - -```java -public class CuratorZookeeperClient extends AbstractZookeeperClient { - public CuratorZookeeperClient(URL url) { - ... - client = builder.build(); - // dubbo对接zk连接状态监听器 - client.getConnectionStateListenable().addListener(new ConnectionStateListener() { - @Override - public void stateChanged(CuratorFramework client, ConnectionState state) { - if (state == ConnectionState.LOST) { - CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED); - } else if (state == ConnectionState.CONNECTED) { - CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED); - } else if (state == ConnectionState.RECONNECTED) { - CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED); - } - } - }); - client.start(); - ... - } -} -``` - -3. 重试任务:注册重新失败重连任务FailbackRegistry中的DubboRegistryFailedRetryTimer,默认5秒检查一次是否需要失败恢复 - -```java -public FailbackRegistry(URL url) { - super(url); - this.retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD); - this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - // Check and connect to the registry - try { - // failedRegistered失败注册重试,failedUnregistered失败注销重试,failedSubscribed失败订阅重试,failedUnsubscribed失败取消订阅重试,failedNotified失败通知重试 - retry(); - } catch (Throwable t) { // Defensive fault tolerance - logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t); - } - } - }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS); -} - -``` - -#### 总结一波 -通过三部分的代码我们可以推断,如果zk状态监听与恢复部分出现问题可能会导致数据丢失问题。于是查看相关的api并且尝试查看dubbo社区的问题与bug,果然发现了类似问题的修改与原因分析:[https://github.com/apache/dubbo/pull/5135](https://github.com/apache/dubbo/pull/5135) - -原因: -> 如果ZNode数据已经存在,在会话超时期间,此时我们将重建一个数据节点,这个重复的异常原因可能是由于zk server中老的超时会话依然持有节点导致该节点的delete删除事件延迟,并且zk server还没有来得及去执行删除,可能由这种场景引起。在这个情景下,我们可以本地删除节点后再创建恢复节点数据。 - -其实说白了: -> 如果会话断开连接又重新连接成功。断开连接发出的删除节点事件,**因为延迟原因走在了重新连接恢复节点事件的后面**。**导致重新连接后没能成功恢复节点**。也就是我么见到的,provider有三个节点服务正常,但是zk注册中心中的提供者节点数据丢失,导致出现该节点对其他订阅者不可见的现象 - -```java - protected void createEphemeral(String path, String data) { - byte[] dataBytes = data.getBytes(CHARSET); - - try { - ((ACLBackgroundPathAndBytesable)this.client.create().withMode(CreateMode.EPHEMERAL)).forPath(path, dataBytes); - } catch (NodeExistsException var5) { - logger.warn("ZNode " + path + " already exists, since we will only try to recreate a node on a session expiration, this duplication might be caused by a delete delay from the zk server, which means the old expired session may still holds this ZNode and the server just hasn't got time to do the deletion. In this case, we can just try to delete and create again.", var5); - this.deletePath(path); - this.createEphemeral(path, data); - } catch (Exception var6) { - throw new IllegalStateException(var6.getMessage(), var6); - } - - } -``` - -**人家源码的warn写的是真清楚**... - -### 定位cpu过高的线程或者位置 - -> 这里要用到几个命令了,比如top,jstack等。 - -#### 查看一波 -- 终端输入`top -H` - -![](https://imgkr.cn-bj.ufileos.com/e69d6335-c260-4d69-9f4d-840b7427cce5.png) -- 类似于长这样,但是有些含义就不解释了,请上互联网 -- 我就挑它吧`1227027` -- 终端输入`top -Hp 1227027` -![](https://imgkr.cn-bj.ufileos.com/d703c8ae-a3a8-4eed-ad57-6c3440dd4ea7.png) -- 这里我也就不介绍了,找一个`1227029` -- `printf "%x\n" 1227029`结果是:`12b915` -- `jstack -l 1227027| grep 0x12b915 -A 10` - -```shell -"DestroyJavaVM" #84 prio=5 os_prio=0 tid=0x00007f69f800b7c0 nid=0x12b915 waiting on condition [0x0000000000000000] - java.lang.Thread.State: RUNNABLE - - Locked ownable synchronizers: - - None - -"BrokerFastFailureScheduledThread1" #83 prio=5 os_prio=0 tid=0x00007f69f8b29050 nid=0x12ba0a runnable [0x00007f68b6bec000] - java.lang.Thread.State: TIMED_WAITING (parking) - at sun.misc.Unsafe.park(Native Method) - - parking to wait for <0x0000000080594a60> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) - at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) -``` -**以上就是举个例子** ... \ No newline at end of file diff --git "a/Interview/codes/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" "b/Interview/codes/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" deleted file mode 100644 index d869ac93..00000000 --- "a/Interview/codes/bus/\344\270\232\345\212\241\351\200\273\350\276\221SQL\350\257\255\345\217\245.md" +++ /dev/null @@ -1,210 +0,0 @@ -## Explain - -> 分别对应: -> -> id select_type table type possible_keys key key_len ref rows Extra -> -> 参考:[https://juejin.im/post/5ec4e4a5e51d45786973b357](https://juejin.im/post/5ec4e4a5e51d45786973b357) - -## 用户服务 - -### checkUsername - -```sql -SELECT - * -FROM - sb_user su -WHERE - user_name = 'mai'; -``` - -- 唯一索引 - -``` -1 "SIMPLE" "su" "const" "user_name" "user_name" "152" "const" 1 "" -``` - -- 普通索引 - -```sql -1 "SIMPLE" "su" "ref" "user_name" "user_name" "152" "const" 1 "Using index condition" -``` - -- 不加索引 - -```sql -1 "SIMPLE" "su" "ALL" NULL NULL NULL NULL 11 "Using where" -``` - -### register省略 - -### login省略 - -### getUserById省略 - -### updateUserInfo省略 - -## 班车服务 - -### getBus省略 - -### getCount - -```sql -SELECT - sc.uuid, - sc.begin_date, - sc.begin_time, - sc.bus_id, - sc.bus_status, - sc.seat_status -FROM - sb_count sc -WHERE - sc.begin_date = '2020-06-03' - AND sc.begin_time >= '14:00' - AND sc.bus_status = '1'; -``` - -- 不加索引 - -``` -1 "SIMPLE" "sc" "ALL" NULL NULL NULL NULL 581 "Using where" - -53ms -``` - -- 给begin_date加普通索引 - -``` -1 "SIMPLE" "sc" "ref" "begin_date" "begin_date" "402" "const" 17 "Using index condition; Using where" - -53ms // 数据太少,体现不出来... -``` - -- 给begin_time加普通索引 - -``` -1 "SIMPLE" "sc" "ref" "begin_date,begin_time" "begin_date" "402" "const" 17 "Using index condition; Using where" - -53ms -``` - -- 给bus_status加普通索引 - -``` -1 "SIMPLE" "sc" "index_merge" "begin_date,begin_time,bus_status" "begin_date,bus_status" "402,152" NULL 7 "Using intersect(begin_date,bus_status); Using where" - -53ms -``` - -### getCountDetailById省略 - -## 订单服务 - -### getNoTakeOrdersById - -```sql -SELECT - so.uuid, - so.bus_status, - so.seats_ids, - so.order_user, - so.order_status, - so.order_time, - sc.bus_id, - CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time -FROM - sb_order so - LEFT JOIN sb_count sc ON so.count_id = sc.uuid -WHERE - so.user_id = '4' - AND so.order_status = '1' - AND sc.begin_date >= '2020-06-03' - AND sc.begin_time >= '15:00'; -``` - -- 不加普通索引 - -``` -1 "SIMPLE" "so" "ALL" NULL NULL NULL NULL 45 "Using where" -1 "SIMPLE" "sc" "eq_ref" "PRIMARY,begin_date,begin_time" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" -``` - -- user_id+order_status加普通索引 - -``` -1 "SIMPLE" "so" "ref" "order_status,user_id" "order_status" "152" "const" 10 "Using index condition; Using where" -1 "SIMPLE" "sc" "eq_ref" "PRIMARY,begin_date,begin_time" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" -``` - -### getEvaluateOrdersById - -```sql -SELECT - so.uuid, - so.bus_status, - so.seats_ids, - so.order_user, - so.order_time, - sc.bus_id, - so.evaluate_status, - so.comment, - CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time -FROM - sb_order so - LEFT JOIN sb_count sc ON so.count_id = sc.uuid -WHERE - so.user_id = 4 - AND so.evaluate_status = "1" - AND so.order_status = "1" - AND (sc.begin_date = "2020-05-30" AND sc.begin_time < "21:00" OR sc.begin_date < "2020-05-30") -``` - -- 带了普通索引 - -``` -1 "SIMPLE" "so" "ref" "user_id_order_eval_status" "user_id_order_eval_status" "312" "const,const,const" 1 "Using index condition" -1 "SIMPLE" "sc" "eq_ref" "PRIMARY" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" -``` - -### getNoPayOrdersById - -```sql -SELECT - so.uuid, - so.bus_status, - so.seats_ids, - so.order_user, - so.order_status, - so.order_time, - sc.bus_id, - CONCAT(sc.begin_date, ' ', sc.begin_time) begin_time -FROM - sb_order so - LEFT JOIN sb_count sc ON so.count_id = sc.uuid -WHERE - so.user_id = '4' - AND so.order_status = '0' - AND sc.begin_date >= '2020-06-03' - AND sc.begin_time >= '20:00'; -``` - -- 带了普通索引 - -``` -1 "SIMPLE" "so" "ref" "order_status,user_id" "order_status" "152" "const" 8 "Using index condition; Using where" -1 "SIMPLE" "sc" "eq_ref" "PRIMARY,begin_date,begin_time" "PRIMARY" "8" "school_bus.so.count_id" 1 "Using where" -``` - -### addOrder省略 - -### selectOrderById省略 - -### updateOrderStatus省略 - -### deleteOrderById省略 - -## 支付服务省略 - diff --git "a/Interview/codes/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" "b/Interview/codes/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" deleted file mode 100644 index 4f1bfb8b..00000000 --- "a/Interview/codes/bus/\346\224\257\344\273\230\346\234\215\345\212\241.md" +++ /dev/null @@ -1,141 +0,0 @@ -# 支付服务 - -## PayController - -### pay -> 省略 - -### payBack -> 退款 -```java -// 删除个人详细信息缓存 -if (redisUtils.hasKey(userInfoKey)) { - redisUtils.del(userInfoKey); -} -// 删除未支付订单列表缓存 -if (redisUtils.hasKey(noTakeKey)) { - redisUtils.del(noTakeKey); -} -// 删除订单详情缓存 -if (redisUtils.hasKey(selectOrderKey)) { - redisUtils.del(selectOrderKey); -} -// 删除场次详情缓存 -if (redisUtils.hasKey(countDetailKey)) { - redisUtils.del(countDetailKey); -} -``` - -## PayServiceImpl - -### pay -```java -public PayResponse pay(PayRequset requset) { - PayResponse payResponse = new PayResponse(); - Long userId = requset.getUserId(); - Double userMoney = null; - try { - // 1. 先核对支付密码是否正确 - tag = MqTags.PAY_CHECK_CANCLE.getTag(); - String key = RedisConstants.USER_INFO_EXPIRE.getKey() + userId; - UserResponse userResponse = new UserResponse(); - if (redisUtils.hasKey(key)) { - userResponse = (UserResponse) redisUtils.get(key); - } else { - UserRequest request = new UserRequest(); - request.setId(userId); - // 获取用户信息 - userResponse = userService.getUserById(request); - } - - // 支付密码不对 - if (!userResponse.getUserDto().getPayPassword().equals(requset.getPayPassword())) { - payResponse.setCode(SbCode.PAY_PASSWORD_ERROR.getCode()); - payResponse.setMsg(SbCode.PAY_PASSWORD_ERROR.getMessage()); - return payResponse; - } - // 2。 核对余额是否够 - userMoney = userResponse.getUserDto().getMoney(); - Double subMoney = NumberUtil.sub(userMoney, requset.getTotalMoney()); - BigDecimal round = NumberUtil.round(subMoney, 2); - if (round.doubleValue() < 0) { - payResponse.setCode(SbCode.MONEY_ERROR.getCode()); - payResponse.setMsg(SbCode.MONEY_ERROR.getMessage()); - return payResponse; - } - // 3。 够,就写入 - UserUpdateInfoRequest request = new UserUpdateInfoRequest(); - request.setId(userId); - request.setMoney(round.doubleValue()); - tag = MqTags.PAY_MONEY_CANCLE.getTag(); - userService.updateUserInfo(request); // 暂时先不接受返回信息 - // 模拟异常 -// CastException.cast(SbCode.SYSTEM_ERROR); - payResponse.setCode(SbCode.SUCCESS.getCode()); - payResponse.setMsg(SbCode.SUCCESS.getMessage()); - // 4. 按道理讲,这边更改订单状态...... - return payResponse; - } catch (Exception e) { - log.error("支付业务发生异常"); - MQDto mqDto = new MQDto(); - mqDto.setUserId(userId); - mqDto.setUserMoney(userMoney); - // 发送消息 - try { - String key = RedisConstants.PAY_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(userId); - sendCancelPay(topic,tag,key, JSON.toJSONString(mqDto)); - log.warn("支付回退消息已发送"); - } catch (Exception ex) { - ex.printStackTrace(); - log.error("支付消息都崩的话..."); - } - payResponse.setCode(SbCode.SYSTEM_ERROR.getCode()); - payResponse.setMsg(SbCode.SYSTEM_ERROR.getMessage()); - return payResponse; - } - -} -``` - -### payBack - -```java -public PayResponse payBack(PayBackRequest request) { - PayResponse response = new PayResponse(); - try { - // 1. 退回金额 - // 读用户金额 - UserRequest userRequest = new UserRequest(); - userRequest.setId(request.getUserId()); - UserResponse userResponse = userService.getUserById(userRequest); - UserDto userDto = userResponse.getUserDto(); - // 计算金额 - BigDecimal add = NumberUtil.add(userDto.getMoney() + request.getTotalMoney()); - BigDecimal round = NumberUtil.round(add, 2); - // 写回 - UserUpdateInfoRequest userUpdateInfoRequest = new UserUpdateInfoRequest(); - userUpdateInfoRequest.setId(request.getUserId()); - userUpdateInfoRequest.setMoney(round.doubleValue()); - userService.updateUserInfo(userUpdateInfoRequest); - // 2. 退回座位 - busService.filterRepeatSeats(request.getSeatsIds(), request.getCoundId()); - // 3. 更改订单状态:关闭 - OrderRequest orderRequest = new OrderRequest(); - orderRequest.setUuid(request.getOrderId()); - orderRequest.setOrderStatus("3"); - orderService.updateOrderStatus(orderRequest); - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - return response; - } catch (Exception e) { -// e.printStackTrace(); - log.error("退款业务异常"); - // 这里可以发消息, 此处先省略 - response.setCode(SbCode.SYSTEM_ERROR.getCode()); - response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); - return response; - } -} -``` - -这两个业务逻辑异常都采用了消息队列,日后再写。 \ No newline at end of file diff --git "a/Interview/codes/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" "b/Interview/codes/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" deleted file mode 100644 index e6839718..00000000 --- "a/Interview/codes/bus/\347\216\257\345\242\203\346\220\255\345\273\272\346\226\207\346\241\243.md" +++ /dev/null @@ -1,64 +0,0 @@ -# 项目环境搭建 -> 主要搭建的技术:Java、MySQL、Redis、zookeeper、Dubbo、RocketMQ - -## 引言 -首先说明,MySQL和Redis放到了我的阿里云服务器,负载并不大,其他全部服务搭建在实验室的服务器主机上,并采用内网穿透映射端口,让大家访问,大家注意一点哈。请谅解。 - -还有,本项目是对本校的班车预约服务的架构进行了重构,采用的比较热门的技术,如README所示。 - -## 技术选型 - - -![选型介绍图](https://imgkr.cn-bj.ufileos.com/56250623-c12e-4440-abe9-bc0277c4e3be.png) - -从选型图上可以清晰的看到: -- **Web层**:sb-gateway网关,控制整个项目的请求分发与转接等。 -- **接口层**:sb-api定义各个服务的接口,比如用户、班车、订单等。 -- **服务层**:有着四大服务,分别如上图所示,用户、班车、订单和支付,并且其中均采用第三方中间件Zookeeper、Dubbo和RocketMQ等。 -- **存储层**,实际上,MySQL作为存储,而Redis做为MySQL上一层的缓存层 - -## 架构路线 - - -![架构路线图](https://imgkr.cn-bj.ufileos.com/18606af1-be51-44ce-9c06-f2abff84a294.png) - -首先,一条请求从用户移动端或者PC端发出,经过前端的Nginx的代理转发到sb-gateway的网关,网关进一步的将请求进行路由分发到对应的Controller,从而执行相应的业务逻辑,其中对应的Controller中分为两步,第一步:找缓存,若存在,直接返回,若不存在。第二步:业务逻辑方法向Dubbo请求远程调用(RPC),得到结果,另外一边业务执行相应的底层如MySQL等,将结果返回给网关,并写入缓存等。 - -## 环境端口 - - -![端口定义](https://imgkr.cn-bj.ufileos.com/9a949053-2417-468a-b7cb-0490c5c78b51.png) - - -![环境启动](https://imgkr.cn-bj.ufileos.com/53607d10-75a7-47bc-815c-adbeef4193f5.png) - - - -## Java -> 我在博客上写好了,可以参考。[http://dreamcat.ink/2019/07/08/windows-mac-he-linux-an-zhuang-java/](http://dreamcat.ink/2019/07/08/windows-mac-he-linux-an-zhuang-java/) - -## MySQL -> 我也在博客上写好了,可以参考,项目采用的MySQL8版本。[http://dreamcat.ink/2019/05/27/windows-mac-he-linux-an-zhuang-mysql/](http://dreamcat.ink/2019/05/27/windows-mac-he-linux-an-zhuang-mysql/) - -## Redis -> 我还在博客上写好了,可以参考。[http://dreamcat.ink/2019/05/28/windows-mac-he-linux-an-zhuang-redis/](http://dreamcat.ink/2019/05/28/windows-mac-he-linux-an-zhuang-redis/) - -## zookeeper -- [下载地址](https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/) -- 选择3.4.14版本下载即可,放进和刚才的myapps下。 -- 解压:tar -zxf zookeeper-3.4.14.tar.gz -- 将配置文件cp -r zoo_sample.cfg zoo.cfg -- 启动zookeeper./zkServer.sh start - -## Dubbo -- [采用的是最新版本](https://github.com/apache/dubbo-admin) -- 下载解压后修配置文件 `dubbo-admin-server/src/main/resources/application.properties` -- 配置文件修改`zookeeper`地址,dubbo控制台端口默认8080,(参考端口图) -- 可以修改为其他端口例如 server.port=9898,以免与其他服务端口冲突。(参考端口图) -- 在主目录dubbo-admin-server目录下,执行 `mvn clean package -Dmaven.test.skip=true` -- `cd dubbo-admin-server/target java -jar dubbo-admin-0.1.jar` 启动即可 -- 最新版本采用前后端分离,那么前端可以到`dubbo-admin-ui`目录下,可在config目录下找到index.js中修改端口,修改过后可以终端输入`npm install` -- 安装依赖过后,可以`npm run dev`启动 - -## RocketMQ -> 我早有记录:[http://dreamcat.ink/2020/02/21/centos7-an-zhuang-rocketmq-ji-pei-zhi/](http://dreamcat.ink/2020/02/21/centos7-an-zhuang-rocketmq-ji-pei-zhi/) \ No newline at end of file diff --git "a/Interview/codes/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" "b/Interview/codes/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" deleted file mode 100644 index 070b58fc..00000000 --- "a/Interview/codes/bus/\347\217\255\350\275\246\346\234\215\345\212\241.md" +++ /dev/null @@ -1,357 +0,0 @@ -# 班车服务 - -## BusController - -### getCount -> 这部分缓存,优化的时候采用了Redis的list,发现我用Jmeter测试并发的时候,发现了Redis中出现了非常多的异常数据,我当时还没有找到问题,暂时先采取以下方案,以下方案在并发时候,并没有出现数据异常。 -```java -if (redisUtils.hasKey(key)) { - // 如果缓存存在 - Object obj = redisUtils.get(key); - log.warn("getCount->redis\n"); - // 返回数据 - return new ResponseUtil().setData(obj); -} - -// 写 -if (!redisUtils.hasKey(key)) { - // 如果缓存不存在,就写,注意与数据库数据一致性 - redisUtils.set(key, response, RedisConstants.COUNTS_EXPIRE.getTime()); -} -``` - -### getCountDetailById -```java -// 和上面一样 -if (redisUtils.hasKey(key)) { - Object obj = redisUtils.get(key); - log.warn("getCountDetailById->redis\n"); - return new ResponseUtil().setData(obj); -} -// 这里不判断了,上面已经判断过了 -redisUtils.set(key, response, RedisConstants.COUNT_DETAIL_EXPIRE.getTime()); -``` - -## BusServiceImpl -### getBus -> 这里基本没调用过,它是获取班车人物信息的。所以分页查询即可 - -```java -// MyBatis plus的分页查询 -IPage busIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -busIPage = busMapper.selectPage(busIPage, null); -``` - -### getCount -> 获取场次列表 - -```java -// 分页插件,这里自定义分页查询 -IPage countIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取时间 -String currHours = DateUtil.getHours(); -String day = DateUtil.getDay(); -System.out.println("当前时间:"+currHours); -System.out.println("当前日期:"+day); -// 判断条件 -// 1. 找出符合当前天(比如,5.30) -// 2. 找出大于等于当前时间的场次(比如,数据库8点有一场,目前时间为7点,它就符合) -// 3. 找出状态为getBusStatus的场次,一般是还未发车的场次,(比如0,1) -queryWrapper - .eq("begin_date", day) - .ge("begin_time", currHours) - .eq("bus_status", request.getBusStatus()) - .orderByAsc("begin_time");// 时间 -countIPage = countMapper.selectCounts(countIPage, queryWrapper); -``` -**自定义分页CountMapper接口** -```java -public interface CountMapper extends BaseMapper { - /** - * - * @param page - * @param wrapper - * @return - */ - IPage selectCounts(IPage page, @Param(Constants.WRAPPER) Wrapper wrapper); - - /** - * - * @param wrapper - * @return - */ - CountDetailDto selectCountDetailById(@Param(Constants.WRAPPER) Wrapper wrapper); -} -``` -**xml** -```xml - -``` - -### getCountDetailById -> 查询场次详情信息 -```java -// 自定义查询,涉及到联表查询 -QueryWrapper wrapper = new QueryWrapper<>(); -wrapper.eq("sc.uuid", request.getCountId()); -``` -**xml** -```xml - -``` - -### repeatSeats -> 判断座位是否已重复 -```java -public boolean repeatSeats(String seats, Long coundId) { - // 查查数据库, 找到座位字段 - boolean b = false; // false:不重复,true:重复 - try { - Count count = countMapper.selectById(coundId); - // 比如,selectedSeats 是1,2 - // dbSeats:"", - // dbSeats:"1,2,3", - // dbSeats: "4,5" - // 前端传来的selectedSeats, 前端判断是否为空,要不然后端也判断一下得了 - if (seats.equals("")) { - return true; - } - if (count.getSelectedSeats().equals("")) { - return false; - } - String[] ss = seats.split(","); - String[] cs = count.getSelectedSeats().split(","); - // 这里考虑并发问题 - HashSet hashSet = new HashSet<>(Arrays.asList(cs)); - for (String s : ss) { - if (hashSet.contains(s)) return true; - } - } catch (Exception e) { - e.printStackTrace(); - log.error("selectedSeats", e); - return true; // 异常就算是重复 - } - return b; -} -``` -### addSeats -```java -if (!StrUtil.isEmpty(selectedSeats)) { - // 这里可以优化,字符串拼接,这样的方式爆内存 - // StringBuffer - newSelectedSeats = selectedSeats + "," + newSelectedSeats; -} -``` - -### filterRepeatSeats -> 回退座位 -```java -Count count = countMapper.selectById(coundId); -String[] ss = seats.split(","); -String[] cs = count.getSelectedSeats().split(","); -// 并发问题,注意 -HashSet hashSet = new HashSet<>(Arrays.asList(cs)); -for (String s : ss) { - if (hashSet.contains(s)) { - hashSet.remove(s); - } -} -if (hashSet.isEmpty()) { - count.setSelectedSeats(""); -} -// 考虑了并发 -StringBuffer sb = new StringBuffer(); -for (String s : hashSet) { - sb.append(s); - sb.append(","); -} -// 上面的方案可以用String的replace的方法替换,遍历要回退的座位,依次替换即可 -count.setSelectedSeats(sb.toString()); -countMapper.updateById(count); -``` -看一下String的replace的源码(1.8),感觉遍历还挺多,但的确不用自己去写了 -```java -public String replace(char oldChar, char newChar) { - if (oldChar != newChar) { - int len = value.length; - int i = -1; - char[] val = value; /* avoid getfield opcode */ - - while (++i < len) { - if (val[i] == oldChar) { - break; - } - } - if (i < len) { - char buf[] = new char[len]; - for (int j = 0; j < i; j++) { - buf[j] = val[j]; - } - while (i < len) { - char c = val[i]; - buf[i] = (c == oldChar) ? newChar : c; - i++; - } - return new String(buf, true); - } - } - return this; -} -``` - -## BusSchedule定时器 -> 这里可以使用redis的延时队列或者RocketMQ的消息队列 - -### schedulChangeBusStatus - -```java -/** - * 每天上午7点到晚上21点,每隔30分钟执行一次 - */ -@Scheduled(cron = "0 0/30 7-21 * * ?") -private void schedulChangeBusStatus() { - log.warn("schedulChangeBusStatus执行"); - busService.schedulChangeBusStatus(); -} -``` -看一下业务逻辑 -```java -public void schedulChangeBusStatus() { - // 获取时间 - String currTime = DateUtil.getHours(); - // 获取日期 - String day = DateUtil.getDay(); - log.warn("schedulChangeBusStatus->目前时间:" + currTime); - log.warn("schedulChangeBusStatus->目前时间:" + day); - System.out.println("目前时间:"+ currTime); - System.out.println("目前时间:"+ day); - QueryWrapper queryWrapper = new QueryWrapper<>(); - // 先取出beingtime和now相等的表或者end_time和now相等到表 - // 1. 取出当天 - // 2. 取出开始时间或者结束时间符合当前时间 - queryWrapper - .eq("begin_date", day) // 取出当天 - .and(o -> o.eq("begin_time", currTime) // 当前时间 - .or() - .eq("end_time", currTime)); - List counts = countMapper.selectList(queryWrapper); - log.warn("schedulChangeBusStatus->查询到的:" + counts.toString()); - // 开始作妖 - for (Count count : counts) { - String busStatus = count.getBusStatus(); - String beginTime = count.getBeginTime(); - String endTime = count.getEndTime(); - if (currTime.equals(beginTime)) { - if (busStatus.equals("0")) { // 沙河空闲 - count.setBusStatus("2"); // 沙河->清水河 - } - if (busStatus.equals("1")) { // 清水河空闲 - count.setBusStatus("3"); // 清水河->沙河 - } - count.setSelectedSeats(""); // 清空座位 - } - if (currTime.equals(endTime)) { - if (busStatus.equals("2")) { // 沙河->清水河 - count.setBusStatus("1"); // 清水河空闲 - } - if (busStatus.equals("3")) { // 清水河->沙河 - count.setBusStatus("0"); // 沙河空闲 - } - } - System.out.println("修改的:" + count); - log.warn("schedulChangeBusStatus->修改的:" + count); - // 写入数据库 - countMapper.updateById(count); - } - // 删缓存,这里非常重要...不删后果很严重 - String key1 = RedisConstants.COUNTS_EXPIRE + "0"; - String key2 = RedisConstants.COUNTS_EXPIRE + "1"; - if (redisUtils.hasKey(key1)) { - redisUtils.del(key1); - } - if (redisUtils.hasKey(key2)) { - redisUtils.del(key2); - } -} -``` -> 这里每隔30分钟扫库进行一次io,这里可以有优化... 假如我使用Redis的延迟队列 -1. 在用定时器添加场次的时候,可以将这些场次存入Redis的zset的队列中,元素为场次ID,元素对应的score是出发时间,这样就有17个(定时器每天凌晨添加17个) -2. 还是用定时器轮询,采用每隔半小时轮询一次,我们取出队列中当前时间大于队列中权重的时间的场次ID,开始进行业务逻辑判断(更改场次状态) -3. 更改过后,删除或者更改该场次的score的时间(结束时间) -4. 以此类推,如果是结束时间的话,直接删除该场次id和score。 - -### addCounts -> 这个项目,没有后台,因此场次需要定时器添加即可 -```java -/** - * 每天凌晨0点2分执行 - */ -@Scheduled(cron = "0 2 0 * * ? ") -private void addCounts(){ - log.warn("addCounts执行"); - busService.addCounts(); -} -``` -具体的业务逻辑: -```java -public void addCounts() { - // 获取日期 - String day = DateUtil.getDay(); - // 获取前17个场次 - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.last("limit 17"); - List counts = countMapper.selectList(wrapper); - // 开始修改 这里可以用java8 的特性, 还不是很熟悉,后期优化一下 - for (Count count : counts) { - // 更改日期 - count.setBeginDate(day); - // 更改uuid - count.setUuid(UUIDUtils.flakesUUID()); - // 清空座位 - count.setSelectedSeats(""); - // 将走位状态清零 - count.setSeatStatus("0"); - // 插入 - countMapper.insert(count); - } - - // 删缓存,不删后果依然很严重 - String key1 = RedisConstants.COUNTS_EXPIRE + "0"; - String key2 = RedisConstants.COUNTS_EXPIRE + "1"; - if (redisUtils.hasKey(key1)) { - redisUtils.del(key1); - } - if (redisUtils.hasKey(key2)) { - redisUtils.del(key2); - } -} - -``` diff --git "a/Interview/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/Interview/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" deleted file mode 100644 index be1fc864..00000000 --- "a/Interview/codes/bus/\347\224\250\346\210\267\346\234\215\345\212\241.md" +++ /dev/null @@ -1,74 +0,0 @@ -# 用户服务 - - -## UserController - -### checkUsername -> 这个我就不需要再说了,这个没啥可说的 - - -### register -> 这个注意请求体的参数即可,也没什么可说的 - - -### getUserById -> 注意缓存是否存在,存在直接返回即可,没有就调用业务`getUserById`获取结果 - -### updateUserInfo -> 注意请求体的参数即可,并且若是更新数据,那么自然要删除UserInfo的缓存,保持数据一致,写不写缓存这一点都行,下次请求getUserById会写缓存。 - -### logout -> 注意redis删除缓存即可 - - -## UserServiceImpl - -### checkUsername -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 查询user_name列与getUsername相匹配的数据 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -``` - -### regsiter -```java -// 密码采用了md5加密 -String md5Password = MD5Util.encrypt(user.getUserPwd()); -user.setUserPwd(md5Password); -``` - -### login -```java -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 根据user_name查询用户是否存在 -queryWrapper.eq("user_name", request.getUsername()); -User user = userMapper.selectOne(queryWrapper); -if (user != null && user.getUuid() > 0) { - // 用户不为空,用户id > 0 - String md5Password = MD5Util.encrypt(request.getPassword()); - if (user.getUserPwd().equals(md5Password)) { - // 数据库密码和所加密的md5密码一致 - res.setUserId(user.getUuid()); - res.setCode(SbCode.SUCCESS.getCode()); - res.setMsg(SbCode.SUCCESS.getMessage()); - } -} -``` - -### getUserById -```java -//根据id查询用户 -User user = userMapper.selectById(request.getId()); -UserDto userDto = userConverter.User2Res(user); -``` - -### updateUserInfo -```java -// 这里一采用了mapstruct映射,有兴趣的可以去网上找资料学习学习 -User user = userConverter.res2User(request); -``` - -## 总结 - -用户服务,没有什么比较复杂的业务,复杂的业务在订单服务、场次服务、支付服务、 diff --git "a/Interview/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" "b/Interview/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" deleted file mode 100644 index 857d0fbe..00000000 --- "a/Interview/codes/bus/\350\256\242\345\215\225\346\234\215\345\212\241.md" +++ /dev/null @@ -1,282 +0,0 @@ -# 订单服务 -## OrderController - -### getNoTakeOrdersById -> 未乘坐订单 - -```java -if (redisUtils.hasKey(key)) { - // redis是否有该缓存 - Object obj = redisUtils.get(key); - NoTakeBusResponse response = (NoTakeBusResponse) obj; - for (NoTakeDto noTakeDto : response.getNoTakeDtos()) { - // 如果场次发车时间-当前时间是大0的,那么说明已经发车了 - String beginTime = noTakeDto.getBeginTime(); - if (beginTime.compareTo(DateUtil.getHours()) <= -1) { - // 删掉当前缓存 - redisUtils.del(key); - // 重新获取最新的数据 - response = orderService.getNoTakeOrdersById(request); - // 写缓存 - redisUtils.set(key, response, RedisConstants.NO_TAKE_OREDERS_EXPIRE.getTime()); - return new ResponseUtil().setData(response); - } - } - log.warn("getNoTakeOrdersById->redis\n"); - return new ResponseUtil().setData(obj); -} -``` - -### getNoPayOrdersById -> 获取未支付订单接口 -```java -// 从redis中是否有缓存 -if (redisUtils.hasKey(key)) { - // 有就获取 - Object obj = redisUtils.get(key); - log.warn("getNoPayOrdersById->redis\n"); - return new ResponseUtil().setData(obj); -} -``` -### getEvaluateOrdersById -> 根据评价状态获取用户订单接口 -略 - -### addOrder -> 添加订单接口 - -```java -String countKey = RedisConstants.COUNT_DETAIL_EXPIRE.getKey() + request.getCountId(); -// 座位缓存失效 -if (redisUtils.hasKey(countKey)) { - redisUtils.del(countKey); -} -String noPayKey = RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey() + userId; -// 未支付列表缓存失效 -if (redisUtils.hasKey(noPayKey)) { - redisUtils.del(noPayKey); -} -``` -### selectOrderById -> 根据订单id获取详情订单 -略 - -### updateOrderStatus -> 更改订单状态 -```java -// 删掉订单详情缓存 -if (redisUtils.hasKey(selectOrderKey)) { - redisUtils.del(selectOrderKey); -} -// 删除未乘坐缓存 -if (redisUtils.hasKey(noTakeKey)) { - redisUtils.del(noTakeKey); -} -// 删除未支付缓存 -if (redisUtils.hasKey(noPayKey)) { - redisUtils.del(noPayKey); -} -// 删除评价缓存 -if (redisUtils.hasKey(evaluateKey)) { - redisUtils.del(evaluateKey); -} -``` - -## OrderServiceImpl - -### getNoTakeOrdersById -```java -IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -// 比如5.30 -String day = DateUtil.getDay(); -// 比如20:00 -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("user_id", request.getUserId()) // 用户id - .eq("order_status", "1")// 1:已经支付 - .ge("sc.begin_date", day) // 比如订单日期大于等于今天 - .ge("sc.begin_time", hours) // 订单时间大于等于当前时间 - .orderByAsc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 排序 -``` - -### getEvaluateOrdersById -```java -IPage orderIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -String day = DateUtil.getDay(); -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("user_id", request.getUserId()) // 用户di - .eq("order_status", "1") // 状态为1 - // 两种情况: - // 1. 符合当天日期,并且订单场次发车时间小于当前日期 - // 2. 订单场次的发车日期小于当前日期 - .and(o -> o.eq("sc.begin_date", day) - .lt("sc.begin_time", hours) - .or().lt("sc.begin_date", day)) - .eq("evaluate_status", request.getEvaluateStatus()) // 评价状态 - .orderByDesc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 排序 -``` - -### getNoPayOrdersById -```java -IPage noPayDtoIPage = new Page<>(request.getCurrentPage(), request.getPageSize()); -QueryWrapper queryWrapper = new QueryWrapper<>(); -// 获取系统年月日 -String day = DateUtil.getDay(); -String hours = DateUtil.getHours(); -System.out.println("当前日期:" + day); -System.out.println("当前时间:" + hours); -queryWrapper - .eq("so.user_id", request.getUserId()) // 用户id - .eq("so.order_status", "0") // 未支付状态 - .ge("sc.begin_date", day) // 比如,订单场次日期大于当前日期 - .ge("sc.begin_time", hours)// 订单场次时间大于当前日期 - .orderByDesc("sc.begin_time") // 排序 - .orderByDesc("so.order_time"); // 未支付 -``` - -### addOrder -```java -public AddOrderResponse addOrder(AddOrderRequest request) { - // 判断座位,如果重复,直接退出,否则更新场次的座位信息 - AddOrderResponse response = new AddOrderResponse(); - // 全局orderId - Long orderId = UUIDUtils.flakesUUID(); - // 1。 判断座位,如果重复,直接退出,否则下一步 - // 2。 更新座位,如果没有异常,这是写操作 - // 3。 计算总金额,如果没有异常 - // 4。 添加订单,如果异常,这是写操作 - try { - // 1。 判断座位,如果重复,直接退出,否则下一步 - tag = MqTags.ORDER_SEATS_CANCEL.getTag(); - boolean repeatSeats = busService.repeatSeats(request.getSeatsIds(), request.getCountId()); - if (repeatSeats) { - // b:true 说明重复 - response.setCode(SbCode.SELECTED_SEATS.getCode()); - response.setMsg(SbCode.SELECTED_SEATS.getMessage()); - return response; - } -// CastException.cast(SbCode.SYSTEM_ERROR); - // 2。 更新座位,如果没有异常,这是写操作 - // 用tags来过滤消息 - tag = MqTags.ORDER_ADD_SEATS_CANCLE.getTag(); - boolean addSeats = busService.addSeats(request.getSeatsIds(), request.getCountId()); - if (!addSeats) { - response.setCode(SbCode.DB_EXCEPTION.getCode()); - response.setMsg(SbCode.DB_EXCEPTION.getMessage()); - return response; - } - // 模拟系统异常 -// CastException.cast(SbCode.SYSTEM_ERROR); - // 3。 计算总金额,如果没有异常 - tag = MqTags.ORDER_CALC_MONEY_CANCLE.getTag(); - String seatIds = request.getSeatsIds(); - Integer seatNumber = seatIds.split(",").length; - Double countPrice = request.getCountPrice(); - Double totalPrice = getTotalPrice(seatNumber, countPrice); - -// CastException.cast(SbCode.SYSTEM_ERROR); - // 4。 添加订单,如果异常,这是写操作 - Order order = orderConvertver.res2Order(request); - order.setOrderPrice(totalPrice); - order.setEvaluateStatus("0"); // 未评价 - order.setOrderStatus("0"); // 未支付 - order.setUuid(orderId); // 唯一id - tag = MqTags.ORDER_ADD_CANCLE.getTag(); - int insert = orderMapper.insert(order);// 插入 不判断了 -// CastException.cast(SbCode.SYSTEM_ERROR); - // 这里就不读了,耗时 -// QueryWrapper wrapper = new QueryWrapper<>(); -// wrapper.eq("so.uuid", order.getUuid()); -// OrderDto orderDto = orderMapper.selectOrderById(wrapper); - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - response.setOrderId(orderId); -// response.setOrderDto(orderDto); - // 这里放redis 未支付缓存,时间前端给定 - redisUtils.set(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + orderId, orderId, request.getExpireTime()); - return response; - } catch (Exception e) { - // 以上操作如果程序都不发生异常的话, 是不会执行这里的代码的 - // 也就是说不会发送回退消息的。 - // 目的是在高并发的情况下,程序内部发生异常,依然高可用 -// e.printStackTrace(); - log.error("订单业务发生异常"); - // 发消息,将座位退回,将订单退回 - MQDto mqDto = new MQDto(); - mqDto.setOrderId(orderId); - mqDto.setCountId(request.getCountId()); - mqDto.setSeatsIds(request.getSeatsIds()); - try { - String key = RedisConstants.ORDER_EXCEPTION_CANCLE_EXPIRE.getKey() + Convert.toStr(orderId); - sendCancelOrder(topic,tag, key, JSON.toJSONString(mqDto)); - log.warn("订单回退消息发送成功..." + mqDto); - } catch (Exception ex) { - ex.printStackTrace(); - } - response.setCode(SbCode.SYSTEM_ERROR.getCode()); - response.setMsg(SbCode.SYSTEM_ERROR.getMessage()); - return response; - } -} -``` - -### selectOrderById -> 省略 - -### updateOrderStatus -```java -public OrderResponse updateOrderStatus(OrderRequest request) { - OrderResponse response = new OrderResponse(); - try { - // 获取orderDto - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("so.uuid", request.getUuid()); - OrderDto orderDto = orderMapper.selectOrderById(wrapper); - // 1, 检查状态是否为2 - if (request.getOrderStatus().equals("2")) { - // 说明关闭订单,回退座位 - busService.filterRepeatSeats(orderDto.getSeatsIds(), orderDto.getCountId()); - redisUtils.del(RedisConstants.COUNT_DETAIL_EXPIRE.getKey() - + orderDto.getCountId()); - // 清除场次详情的缓存 - } - if (request.getOrderStatus().equals("1")) { - // 说明已经支付,删掉5分钟的订单缓存 - redisUtils.del(RedisConstants.ORDER_CANCLE_EXPIRE.getKey() + request.getUuid()); - } - Order order = orderConvertver.res2Order(request); - // 更新状态 - orderMapper.updateById(order); - // 暂时就不获取了 - response.setCode(SbCode.SUCCESS.getCode()); - response.setMsg(SbCode.SUCCESS.getMessage()); - redisUtils.del(RedisConstants.NO_PAY_ORDERS_EXPIRE.getKey()+order.getUserId()); - redisUtils.del(RedisConstants.SELECT_ORDER_EXPIRE.getKey() + request.getUuid()); - } catch (Exception e) { - log.error("updateOrderStatus", e); - response.setCode(SbCode.DB_EXCEPTION.getCode()); - response.setMsg(SbCode.DB_EXCEPTION.getMessage()); - return response; - } - return response; -} -``` - -### deleteOrderById -> 省略 - -### sendCancelOrder -> 发送订单回退消息 - -后边会单独介绍消息队列 \ No newline at end of file diff --git "a/Interview/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 7a8d9fa1..00000000 --- "a/Interview/codes/collection/ArrayList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,123 +0,0 @@ -# ArrayList -## 一些特点 - -- ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入`null`元素,底层通过**数组**实现。 -- 每个ArrayList都有一个容量(capacity),表示**底层数组的实际大小**,容器内存储元素的个数不能多于当前容量。 -- 当向容器中添加元素时,如果容量不足,**容器会自动增大底层数组的大小**。 -- Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。 - -## 底层数据结构 -```java -private static final int DEFAULT_CAPACITY = 10; // 默认容量 -transient Object[] elementData; // Object 数组 -private int size; // 大小 -``` - -## 构造函数 - -### 有参 -```java -public ArrayList(int initialCapacity) { - if (initialCapacity > 0) { - this.elementData = new Object[initialCapacity]; - } else if (initialCapacity == 0) { - this.elementData = EMPTY_ELEMENTDATA; // 默认10 - } else { - throw new IllegalArgumentException("Illegal Capacity: "+ - initialCapacity); - } -} -``` - -### 无参 -```java -public ArrayList() { - this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 默认10 -} -``` - -## 扩容 -常问,看一下源码吧 - -```java -public void ensureCapacity(int minCapacity) { - int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) - // any size if not default element table - ? 0 - // larger than default for default empty table. It's already - // supposed to be at default size. - : DEFAULT_CAPACITY; - - if (minCapacity > minExpand) { - ensureExplicitCapacity(minCapacity); - } -} -``` -继续看ensureExplicitCapacity -```java -private void ensureExplicitCapacity(int minCapacity) { - modCount++; - - // overflow-conscious code - if (minCapacity - elementData.length > 0) - grow(minCapacity); // 扩容最关键的方法 -} -``` -其实可以之和看grow(jdk1.8) - -```java - private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - int newCapacity = oldCapacity + (oldCapacity >> 1); // old + old * 2 就在这... - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - elementData = Arrays.copyOf(elementData, newCapacity); -} -``` - -## add -还是看直接看源码方便一些 -```java -public boolean add(E e) { - // 扩容判断 - ensureCapacityInternal(size + 1); // Increments modCount!! 这个参数,起到并发异常作用。 - elementData[size++] = e; // 这一步非原子性,并发容易出错,好几种情况。 下次分析 - return true; -} -``` - -## get -```java -一般获取元素,第一步都要判断索引是否越界 -public E get(int index) { - rangeCheck(index); // 判断给定索引是否越界 - return elementData(index); -} -``` - -## 聊一下并发容易出现的问题 -### 首先是数组越界,举个例子 -1. 列表大小为9,即size=9 -2. 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。 -3. 线程B此时也进入add方法,它和获取的size的值也为9,也开始调用ensureCapacityInternal方法。 -4. 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。 -5. 线程B也发现需要大小为10,也可以容纳,返回。 -6. 好了,**问题来了哈** -7. 线程A开始进行设置值操作,elementData[size++] = e操作。此时size变为10。 -8. 线程B也开始进行设置值操作,它尝试设置elementData[10] = e, 而elementData没有进行过扩容,它的下标最大为9,于是此时会报出一个数组越界的异常`ArrayIndexOutOfBoundsException`。 - -### null值,举个例子 -1. 列表大小为10,即size=0 -2. 线程A开始添加元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。也就是说,线程挂在了`element[0] = e`上。 -3. 接着线程B刚好也要开始天极爱一个值为B的元素,且走到了第一条的操作。此时线程B获取的size的值依然为0,于是它将B也放在了elementData下标为0的位置上。 -4. **问题来了**,其实上面也是问题,覆盖了。。。 -5. 线程A将size的值增加为1 -6. 线程B开始将size的值增加为2 -7. 当你获取1索引的时候,那不就是null了? - -## Fail-Fast机制 -ArrayList也采用了快速失败的机制,**通过记录modCount参数来实现**。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。 \ No newline at end of file diff --git "a/Interview/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index d8970013..00000000 --- "a/Interview/codes/collection/BlockingQueue\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,158 +0,0 @@ -# BlockingQueue -看一下官方1.8的官方文档 -![已知实现类](https://imgkr.cn-bj.ufileos.com/1c08311d-77a7-4f60-a4dd-2e293f43c8d0.png) - -官方文档告诉我们它们有这些特点: -- BlockingQueue实现被设计为主要用于生产者 - 消费者队列 -- BlockingQueue实现是线程安全的。 所有排队方法使用内部锁或其他形式的并发控制在原子上实现其效果。 -- BlockingQueue方法有四种形式,具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足: - - 一个抛出异常 - - 返回一个特殊值( null或false ,具体取决于操作) - - 第三个程序将无限期地阻止当前线程,直到操作成功为止 - - 在放弃之前只有给定的最大时限。 - -## 看源码之旅 - -### ArrayBlockingQueue -> 看一下官方文档的解释->一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。这是一个经典的“有界缓冲区”,其中固定大小的数组保存由生产者插入的元素并由消费者提取。 创建后,容量无法更改。 尝试put成满的队列的元件将导致在操作阻挡; 尝试take从空队列的元件将类似地阻塞。此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。 公平性通常会降低吞吐量,但会降低变异性并避免饥饿。 - -#### 常见变量 -```java -int count; // 队列元素数量 -final ReentrantLock lock; // 内部线程安全性用了ReentrantLock -private final Condition notEmpty; // takes方法的等待条件 -private final Condition notFull; // puts方法的等待条件 -``` -#### 构造方法 -```java -public ArrayBlockingQueue(int capacity, boolean fair) { - if (capacity <= 0) - throw new IllegalArgumentException(); // 这里就不说了 - this.items = new Object[capacity]; // 初始容量 - lock = new ReentrantLock(fair); // fair参数决定是否公平锁 - notEmpty = lock.newCondition(); // 上面已提到 - notFull = lock.newCondition(); // 上面已提到 -} -``` -#### add -```java -public boolean add(E e) { - return super.add(e); // 该方法添加失败抛出异常 -} -// AbstractQueue -public boolean add(E e) { - if (offer(e)) - return true; - else - throw new IllegalStateException("Queue full"); // 在这 -} -``` -**remove就不分析了** - -#### offer -```java -public boolean offer(E e) { - checkNotNull(e); - final ReentrantLock lock = this.lock; // 上面的add其实内部也调用了offer,当时我还觉得奇怪,add没上锁?。 原来offer上了锁的 - lock.lock(); - try { - if (count == items.length) - return false; // 满了,就false - else { - enqueue(e); // 否则,添加即可 - return true; // 返回true,并不会抛出异常 - } - } finally { - lock.unlock(); - } -} -``` -**poll不分析了** - -#### put -```java -public void put(E e) throws InterruptedException { - checkNotNull(e); // 检查是否为空,为空就抛出空异常 - final ReentrantLock lock = this.lock; // 上锁哦 - lock.lockInterruptibly(); // 锁中断 - try { - while (count == items.length) // 满了,挂起阻塞 - notFull.await(); - enqueue(e); // 否则添加 - } finally { - lock.unlock(); - } -} -``` - -#### take -```java -public E take() throws InterruptedException { - final ReentrantLock lock = this.lock; // 上锁 - lock.lockInterruptibly(); // 锁中断 - try { - while (count == 0) - notEmpty.await(); // 没有元素,挂起 - return dequeue(); - } finally { - lock.unlock(); - } -} -``` - -#### 带时间的offer和poll -> 其实就是用了`long nanos = unit.toNanos(timeout);` - - -### LinkedBlockingQueue -> 四个方法和ArrayBlockingQueue的差不多,就不多分析了。它的特点之一在于,如果不指定容量,那么默认是等于Integer.MAX_VALUE。可以看一下源码 - -#### 常见的参数 -```java -/** The capacity bound, or Integer.MAX_VALUE if none */ -private final int capacity; // 一看注释,就晓得了 -// 还有一些常见的和上面的差不多 -``` - -#### 构造方法 -```java -public LinkedBlockingQueue() { - this(Integer.MAX_VALUE); // 在这里, 嘿嘿嘿。 -} -``` - -### LinkedTransferQueue -> 基于链接节点的无界TransferQueue 。 这个队列相对于任何给定的生产者订购元素FIFO(先进先出)。 队列的头部是那些已经排队的元素是一些生产者的最长时间。 队列的尾部是那些已经在队列上的元素是一些生产者的最短时间。 - -**四个方法就暂时不提了,大部分操作用了cas并且要关注transfer方法** - -先介绍几个标志参数: -```java -private static final int NOW = 0; // for untimed poll, tryTransfer -private static final int ASYNC = 1; // for offer, put, add -private static final int SYNC = 2; // for transfer, take -private static final int TIMED = 3; // for timed poll, tryTransfer -``` - -```java -// 如果可能,立即将元素转移到等待的消费者。 -public boolean tryTransfer(E e) { - return xfer(e, true, NOW, 0) == null; // xfer的now参数 -} -// 还有一个重载参数的,带时间的。 -``` - -```java -// 将元素传输到消费者,必要时等待。 -public void transfer(E e) throws InterruptedException { - if (xfer(e, true, SYNC, 0) != null) { // SYNC - Thread.interrupted(); // failure possible only due to interrupt - throw new InterruptedException(); - } -} -``` -### PriorityBlockingQueue -> 一看名字就是优先级阻塞队列,它的优先级是由堆实现的,所以该类中有很多堆的方法。源码暂时就不看了,很多都是差不多的。 - -### SynchronousQueue -> 官方文档:每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 个人感觉是生产者生产一个元素,消费者必须消费,生产者才能继续生产。内部也维护了一个TransferQueue,其中部分操作是利用cas。源码就不贴了。有兴趣的可以进去看看哦。 diff --git "a/Interview/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 608032e8..00000000 --- "a/Interview/codes/collection/ConcurrentHashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,238 +0,0 @@ -# ConcurrentHashMap -> 1.7和1.8的源码还不太一样,也就是说加锁的方式不一样。 - -## 1.7 -> 分段锁 - -![图示意图](https://i.loli.net/2019/05/08/5cd1d2c5ce95c.jpg) - -### Segment的数据结构 -```java -/** - * Segment 数组,存放数据时首先需要定位到具体的 Segment 中。 - */ -final Segment[] segments; -transient Set keySet; -transient Set> entrySet; -``` - -Segment 是 ConcurrentHashMap 的一个内部类,主要的组成如下: - -```java -static final class Segment extends ReentrantLock implements Serializable { - private static final long serialVersionUID = 2249069246763182397L; - - // 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶 - transient volatile HashEntry[] table; // volatile修饰 - transient int count; - transient int modCount; - transient int threshold; - final float loadFactor; - -} -``` -- 唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。 -- ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 `ReentrantLock`。 -- 不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。 -- **每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。** - -### put -```java -public V put(K key, V value) { - Segment s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key); - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck - (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment - s = ensureSegment(j); - return s.put(key, hash, value, false); -} -``` -通过key定位到Segment,之后在对应的Segment中进行具体的put -```java -final V put(K key, int hash, V value, boolean onlyIfAbsent) { - HashEntry node = tryLock() ? null : - scanAndLockForPut(key, hash, value); // 1. 加锁处理 - V oldValue; - try { - HashEntry[] tab = table; - int index = (tab.length - 1) & hash; - HashEntry first = entryAt(tab, index); - for (HashEntry e = first;;) { - if (e != null) { - K k; - if ((k = e.key) == key || // 2. 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; - ++modCount; - } - break; - } - e = e.next; - } - else { // 3. 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 - if (node != null) - node.setNext(first); - else - node = new HashEntry(hash, key, value, first); - int c = count + 1; - if (c > threshold && tab.length < MAXIMUM_CAPACITY) - rehash(node); - else - setEntryAt(tab, index, node); - ++modCount; - count = c; - oldValue = null; - break; - } - } - } finally { - unlock(); // 4. 解锁 - } - return oldValue; -} -``` -- 虽然HashEntry中的value是用volatile关键字修饰的,但是并不能保证并发的原子性,所以put操作仍然需要加锁处理。 -- 首先第一步的时候会尝试获取锁,如果获取失败肯定就是其他线程存在竞争,则利用 `scanAndLockForPut()` 自旋获取锁。 - - 尝试获取自旋锁 - - 如果重试的次数达到了`MAX_SCAN_RETRIES` 则改为阻塞锁获取,保证能获取成功。 - -总的来说: -- 将当前的Segment中的table通过key的hashcode定位到HashEntry -- 遍历该HashEntry,如果不为空则判断传入的key和当前遍历的key是否相等,相等则覆盖旧的value -- 不为空则需要新建一个HashEntry并加入到Segment中,同时会先判断是否需要扩容 -- 最后会解除在1中所获取当前Segment的锁。 - -### get -```java -public V get(Object key) { - Segment s; // manually integrate access methods to reduce overhead - HashEntry[] tab; - int h = hash(key); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return e.value; - } - } - return null; -} -``` -- 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。 -- 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。 -- ConcurrentHashMap 的 get 方法是非常高效的,**因为整个过程都不需要加锁。** - -## 1.8 -> 1.7 查询遍历链表效率太低。其中抛弃了原有的 Segment 分段锁,而采用了 `CAS + synchronized` 来保证并发安全性 - -### put -```java - final V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = table;;) { // 1. 根据key计算出hashcode - Node f; int n, i, fh; - if (tab == null || (n = tab.length) == 0) // 2. 判断是否需要进行初始化 - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 3. f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功。 - if (casTabAt(tab, i, null, - new Node(hash, key, value, null))) - break; // no lock when adding to empty bin - } - else if ((fh = f.hash) == MOVED) // 4. 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 - tab = helpTransfer(tab, f); - else { - V oldVal = null; - synchronized (f) { // 5. 如果都不满足,则利用synchronized锁写入数据 - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f;; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - oldVal = e.val; - if (!onlyIfAbsent) - e.val = value; - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } - else if (f instanceof TreeBin) { - Node p; - binCount = 2; - if ((p = ((TreeBin)f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) - p.val = value; - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) // 6. 如果数量大于`TREEIFY_THRESHOLD` 则要转换为红黑树。 - treeifyBin(tab, i); - if (oldVal != null) - return oldVal; - break; - } - } - } - addCount(1L, binCount); - return null; - } - -``` -- 根据key计算出hashcode -- 判断是否需要进行初始化 -- f即为当前key定位出的Node,如果为空表示当前位置可以写入数据,**利用CAS尝试写入,失败则自旋保证成功。** -- 如果当前位置的`hashcode == MOVED == -1`,则需要进行扩容 -- 如果都不满足,则利用`synchronized`锁写入数据 -- 如果数量大于TREEIFY_THRESHOLD 则要转换为红黑树。 - -### get -```java - public V get(Object key) { - Node[] tab; Node e, p; int n, eh; K ek; - int h = spread(key.hashCode()); - if ((tab = table) != null && (n = tab.length) > 0 && - (e = tabAt(tab, (n - 1) & h)) != null) { - if ((eh = e.hash) == h) { - if ((ek = e.key) == key || (ek != null && key.equals(ek))) - return e.val; - } - else if (eh < 0) - return (p = e.find(h, key)) != null ? p.val : null; - while ((e = e.next) != null) { - if (e.hash == h && - ((ek = e.key) == key || (ek != null && key.equals(ek)))) - return e.val; - } - } - return null; - } -``` -- 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。 -- 如果是红黑树那就按照树的方式获取值。 -- 就不满足那就按照链表的方式遍历获取值。 - -## 总结 -1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。 \ No newline at end of file diff --git "a/Interview/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a1905e14..00000000 --- "a/Interview/codes/collection/HashMap\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,173 +0,0 @@ -# HashMap -> 谈到这个,真的是面试高频呀,处处问,关键HashMap在jdk1.7和1.8的源码是我不一样的我这边就只放1.8的源码。 - -## 底层结构 - -> HashMap的底层结构是是**数组+链表**。关于为什么是链表,那是因为哈希冲突,采取链表一种方式。 - -## 常见参数 -```java -static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 初始容量 -static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认负载因子0.75 -// 给定的默认容量为16,负载因子为0.75. -// Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容, -// 而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 -static final int TREEIFY_THRESHOLD = 8;// 成为红黑树的阈值,为什么是8? -static final int UNTREEIFY_THRESHOLD = 6; // 红黑树转链表阈值6 -static final int MIN_TREEIFY_CAPACITY = 64; -// 个人感觉,8是经验值,或者链表为8的时候,性能开始下降,大家知道链表查找慢,当元素过多的时候,非常影响性能的。 -// 因此采取为8的时候转成红黑树。元素过少,其实没必要直接红黑树,元素过少,链表查找的时间也不是很能影响性能。 -// 在 hash 函数设计合理的情况下,发生 hash 碰撞 8 次的几率为百万分之 6,概率说话。 -// 6是因为如果 hash 碰撞次数在 8 附近徘徊,会一直发生链表和红黑树的转化,为了预防这种情况的发生。 -``` - -## hash -```java -static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); -} -``` -hash 函数是先拿到通过 key 的 hashcode,是 32 位的 int 值,然后让 hashcode 的高 16 位和低 16 位进行异或操作。这个也叫扰动函数,这么设计有二点原因: -- 一定要尽可能降低 hash 碰撞,越分散越好; -- 算法一定要尽可能高效,因为这是高频操作, 因此采用位运算; - -## put -> 这里介绍1.8的,1.7的其实很简单,被1.8包括了 -```java -final V putVal(int hash, K key, V value, boolean onlyIfAbsent, - boolean evict) { - Node[] tab; Node p; int n, i; - if ((tab = table) == null || (n = tab.length) == 0) // 1. 判断当前桶是否为空,空的就需要初始化(resize中会判断是否进行初始化) - n = (tab = resize()).length; - if ((p = tab[i = (n - 1) & hash]) == null) // 2. 根据当前key的hashcode定位到具体的桶中并判断是否为空,为空则表明没有Hash冲突,就直接在当前位置创建一个新桶 - tab[i] = newNode(hash, key, value, null); - else { - Node e; K k; - if (p.hash == hash && // 3. 如果当前桶有值(Hash冲突),那么就要比较当前桶中的key、key的hashcode与写入的key是否相等,相等就赋值给e,在第8步的时候会统一进行赋值及返回 - ((k = p.key) == key || (key != null && key.equals(k)))) - e = p; - else if (p instanceof TreeNode) // 4. 如果当前桶为红黑树,那就要按照红黑树的方式写入数据 - e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); - else { // 5. 如果是个链表,就需要将当前的key、value封装称一个新节点写入到当前桶的后面形成链表。 - for (int binCount = 0; ; ++binCount) { - if ((e = p.next) == null) { - p.next = newNode(hash, key, value, null); - if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st // 6. 接着判断当前链表的大小是否大于预设的阈值,大于就要转换成为红黑树 -- - treeifyBin(tab, hash); - break; - } - if (e.hash == hash && // 7. 如果在遍历过程中找到key相同时直接退出遍历。 - ((k = e.key) == key || (key != null && key.equals(k)))) - break; - p = e; - } - } - if (e != null) { // existing mapping for key 8. 如果`e != null`就相当于存在相同的key,那就需要将值覆盖。 - V oldValue = e.value; - if (!onlyIfAbsent || oldValue == null) - e.value = value; - afterNodeAccess(e); - return oldValue; - } - } - ++modCount; - if (++size > threshold) // 9. 最后判断是否需要进行扩容。 - resize(); - afterNodeInsertion(evict); - return null; -} -``` -看代码有点晕乎! - -![put过程](https://imgkr.cn-bj.ufileos.com/b2206341-08ce-4e67-87bd-3a467ccac31e.png) - -1. 判断数组是否为空,为空进行初始化; -2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index; -3. 查看 table[index] 是否存在数据,没有数据就构造一个 Node 节点存放在 table[index] 中; - -4. 存在数据,说明发生了 hash 冲突(存在二个节点 key 的 hash 值一样), 继续判断 key 是否相等,相等,用新的 value 替换原数据(onlyIfAbsent 为 false); - -5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中; -6. 如果不是树型节点,创建普通 Node 加入链表中;判断链表长度是否大于 8, 大于的话链表转换为红黑树; - -7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。 - -## get -```java -public V get(Object key) { - Node e; - return (e = getNode(hash(key), key)) == null ? null : e.value; -} -final Node getNode(int hash, Object key) { - Node[] tab; Node first, e; int n; K k; - if ((tab = table) != null && (n = tab.length) > 0 && - (first = tab[(n - 1) & hash]) != null) { - if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) - return first; - if ((e = first.next) != null) { - if (first instanceof TreeNode) - return ((TreeNode)first).getTreeNode(hash, key); - do { - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } while ((e = e.next) != null); - } - } - return null; -} -``` -- 首先将key hash之后取得所定位的桶 -- 如果桶为空,则直接返回null -- 否则判断桶的第一个位置(有可能是链表、红黑树)的key是否为查询的key,是就直接返回value -- 如果第一个不匹配,则判断它的下一个是红黑树还是链表 -- 红黑树就按照树的查找方式返回值 -- 不然就按照链表的方式遍历匹配返回值 - -## 1.8的优化 -1. 数组+链表改成了数组+链表或红黑树; -2. 链表的插入方式从头插法改成了尾插法,简单说就是插入时,如果数组位置上已经有元素,1.7 将新元素放到数组中,原始节点作为新节点的后继节点,1.8 遍历链表,将元素放置到链表的最后; -3. 扩容的时候 1.7 需要对原数组中的元素进行重新 hash 定位在新数组的位置,1.8 采用更简单的判断逻辑,位置不变或索引+旧容量大小; -4. 在插入时,1.7 先判断是否需要扩容,再插入,1.8 先进行插入,插入完成再判断是否需要扩容; - -## 并发问题 -- HashMap扩容的时候会调用resize()方法,就是这里的并发操作容易在一个桶上形成环形链表 -- 这样当获取一个不存在的key时,计算出的index正好是环形链表的下标就会出现死循环。 -- **但是1.7的头插法造成的问题,1.8改变了插入顺序,就解决了这个问题,但是为了内存可见性等安全性,还是需要ConCurrentHashMap** - -### Java 中有 HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全的 Map。 -- HashTable 是直接在操作方法上加 synchronized 关键字,锁住整个数组,粒度比较大 -- Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现 -- ConcurrentHashMap 使用分段锁,降低了锁粒度,让并发度大大提高。(jdk1.8 CAS+ synchronized) - -[死循环分析](https://zhuanlan.zhihu.com/p/67915754) - -## 哈希遍历 - -```java -Iterator> entryIterator = map.entrySet().iterator(); - while (entryIterator.hasNext()) { - Map.Entry next = entryIterator.next(); - System.out.println("key=" + next.getKey() + " value=" + next.getValue()); - } - -Iterator iterator = map.keySet().iterator(); - while (iterator.hasNext()){ - String key = iterator.next(); - System.out.println("key=" + key + " value=" + map.get(key)); - } -``` -- 建议使用第一种,同时可以把key value取出。 -- 第二种还需要通过key取一次key,效率较低。 - -# HashSet -> HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个`private static final Object PRESENT = new Object();`。 HashSet跟HashMap一样,都是一个存放链表的数组。 - -# TreeMap -> TreeMap 是按照 Key 的自然顺序或者 Comprator 的顺序进行排序,内部是通过红黑树来实现。所以要么 key 所属的类实现 Comparable 接口,或者自定义一个实现了 Comparator 接口的比较器,传给 TreeMap 用户 key 的比较。 - -# LinkedHashMap -> LinkedHashMap 内部维护了一个单链表,有头尾节点,同时 LinkedHashMap 节点 Entry 内部除了继承 HashMap 的 Node 属性,还有 before 和 after 用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。 \ No newline at end of file diff --git "a/Interview/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 4a9ae73a..00000000 --- "a/Interview/codes/collection/LinkedList\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,109 +0,0 @@ -# LinkedList -> LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。 关于栈或队列,现在的首选是ArrayDeque,它有着比LinkedList(当作栈或队列使用时)有着更好的性能。 关于特点,想必大家都知道了。 - -## 底层数据 -> LinkedList底层通过双向链表实现。双向链表的每个节点用内部类Node表示。LinkedList通过**first和last引用分别指向链表的第一个和最后一个元素**。注意这里没有所谓的哑元,当链表为空的时候**first和last都指向null**。 -```java -transient int size = 0; -transient Node first; // 经常用 -transient Node last; // 经常用 -// Node是私有的内部类 -private static class Node { - E item; - Node next; // 这里就不多说了 - Node prev; // - - Node(Node prev, E element, Node next) { - this.item = element; - this.next = next; - this.prev = prev; - } -} -``` -构造函数就不用看了... - -## getFirst和getLast -> 这个挺简单的 -```java -public E getFirst() { - final Node f = first; // 第一个节点 - if (f == null) - throw new NoSuchElementException(); - return f.item; -} -public E getLast() { - final Node l = last; // 最后一个节点 - if (l == null) - throw new NoSuchElementException(); - return l.item; -} -``` - -**removeFirst和removeLast**和上面的源码差不多,挺简单。链表的删除,脑子里画图思考思考就明白了,添加也是一样。 - -## add调用的是`linkLast` -```java -void linkLast(E e) { - final Node l = last; // 要在尾部添加,所以要找last - final Node newNode = new Node<>(l, e, null); // last->e->null - last = newNode; - if (l == null) - first = newNode; // 如果l为空,说明没有链表没有元素,那么first和last都指向e, - else - l.next = newNode; // 否则就将l.next指向新节点 - size++; - modCount++; // 依然存在并发危险哦 -} -``` - -## 看一下remove -```java -public boolean remove(Object o) { - if (o == null) { // 将链表存在null的给移除掉 - for (Node x = first; x != null; x = x.next) { - if (x.item == null) { - unlink(x); - return true; - } - } - } else { - for (Node x = first; x != null; x = x.next) { - if (o.equals(x.item)) { // 否则直接遍历与符合当前对象,unlink即可。 - unlink(x); - return true; - } - } - } - return false; -} -``` -## unlink得看 -> 这个脑子思考一下那个图,其实就明白,很源码的话,也没问题的。 -```java -E unlink(Node x) { - // assert x != null; - final E element = x.item; // 当前值 - final Node next = x.next; // 后 - final Node prev = x.prev; // 前 - - if (prev == null) { // 如果x的前是null,说明,x是首节点,直接让first指向next - first = next; - } else { // x不是首节点,意味着pre是有值哦。 - prev.next = next; // prev.next的箭头指向x的next - x.prev = null; // 将x.prev指向null 双向链表? - } - - if (next == null) { // 同上,说明x是最后节点了 - last = prev; // 让last指向x的prev - } else { - next.prev = prev; // 否则x不是最后节点, 让next.prev 指向prev - x.next = null; // x.next 指向null , 双向链表 - } - - x.item = null; // GC回收 - size--; - modCount++; - return element; -} -``` -**其他几个方法的源码中核心都有这些...** \ No newline at end of file diff --git "a/Interview/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" "b/Interview/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" deleted file mode 100644 index 8995e5ea..00000000 --- "a/Interview/codes/collection/\346\211\213\345\206\231LRU\347\256\227\346\263\225.md" +++ /dev/null @@ -1,73 +0,0 @@ -# LRU算法 -> LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。 - -## 一般过程 -![过程图](https://pic4.zhimg.com/80/v2-71b21233c615b1ce899cd4bd3122cbab_1440w.jpg) - -- 新数据插入到链表头部; -- 每当缓存命中(即缓存数据被访问),则将数据移到链表头部; -- 当链表满的时候,将链表尾部的数据丢弃。 - -再来来个具体的过程 -![具体过程](https://pic3.zhimg.com/80/v2-998b52e7534278b364e439bbeaf61d5e_1440w.jpg) - -1. 最开始时,内存空间是空的,因此依次进入A、B、C是没有问题的 -2. 当加入D时,就出现了问题,内存空间不够了,因此根据LRU算法,内存空间中A待的时间最为久远,选择A,将其淘汰 -3. 当再次引用B时,内存空间中的B又处于活跃状态,而C则变成了内存空间中,近段时间最久未使用的 -4. 当再次向内存空间加入E时,这时内存空间又不足了,选择在内存空间中待的最久的C将其淘汰出内存,这时的内存空间存放的对象就是E->B->D - -## 伪代码实现 - -### 思路 -> 双向链表+HashMap,Java中的LinkedHashMap就实现了该算法。 - -### 定义节点 -```java -class Node { - int key; - int value; - Node pre; - Node next; -} -``` -### 定义HashMap -`HashMap map = new HashMap<>();` - -### get -> 其实挺简单的。 -```java -public int get(int key) { - if (map.containsKey(key)) { - Node n = map.get(key); // 获取内存中存在的值,比如A - remove(n); //使用链表的方法,移除该节点 - setHead(n); //依然使用链表的方法,将该节点放入头部 - return n.value; - } - return -1; -} -``` -- 由于当一个节点通过key来访问到这个节点,那么这个节点就是刚被访问了, -- 就把这个节点删除掉,然后放到队列头,这样队列的头部都是最近访问的, -- 队列尾部是最近没有被访问的。 - -### set -```java -public void set(int key, int value) { - if (map.containsKey(key)) { - Node old = map.get(key); - old.value = value; - remove(old); // 移除旧节点 - setHead(old); // 放到队头 - } else { - Node created = new Node(key, value); - if (map.size() >= capacity) { - map.remove(end.key); // clear该key - remove(end); //链表也是依次 - setHead(created); // 将created放入队头 - } else { - setHead(created); // 如果没满,直接放入队头 - } - map.put(key,created); - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" "b/Interview/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" deleted file mode 100644 index b0b28954..00000000 --- "a/Interview/codes/collection/\351\233\206\345\220\210\346\200\273\350\247\210.md" +++ /dev/null @@ -1,15 +0,0 @@ -# 集合总述 -通过查看JDK8的Collection的文档源码: - -![有点凶](https://imgkr.cn-bj.ufileos.com/98753cd6-dba3-4291-83c3-7964b39c5da3.png) - -再看一Map的: - - -![也挺凶](https://imgkr.cn-bj.ufileos.com/0af46521-0eb6-4666-898e-3536cc3b7c4e.png) - -不说了,来个更清晰一点的: - -![清晰uml图](https://www.pdai.tech/_images/java_collections_overview.png) - -因此,容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。关于各自有什么特点,待后边带着源码详细介绍先。 \ No newline at end of file diff --git "a/Interview/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Interview/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 568af31e..00000000 --- "a/Interview/codes/jvm/\345\240\206\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,26 +0,0 @@ -# 堆溢出例子 -> 启动参数:` -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError` - -```java -public class Test { - public static void main(String[] args) { - List list = new ArrayList<>(); - int i = 0; - while (true) { - list.add(new byte[5*1024*1024]); - System.out.println("分配次数:" + (++i)); - } - } -} -``` -结果: -``` -分配次数:1 -分配次数:2 -分配次数:3 -java.lang.OutOfMemoryError: Java heap space -Dumping heap to java_pid39413.hprof ... -Heap dump file created [16921133 bytes in 0.013 secs] -Exception in thread "main" java.lang.OutOfMemoryError: Java heap space - at Test.Test.main(Test.java:18) -``` \ No newline at end of file diff --git "a/Interview/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Interview/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 90dbf83d..00000000 --- "a/Interview/codes/jvm/\345\276\252\347\216\257\345\274\225\347\224\250\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,14 +0,0 @@ -# 引用计数循环引用小例子 - -```java -public class Test { - public static void main(String[] args) { - Object object1 = new Object(); - Object object2 = new Object(); - object1 = object2; // obj1 引用obj2 - object2 = object1; // obj2 引用obj1 循环引用 - object1 = null; - object2 = null; - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" "b/Interview/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" deleted file mode 100644 index 10e93658..00000000 --- "a/Interview/codes/jvm/\346\237\245\347\234\213\345\217\215\347\274\226\350\257\221\346\272\220\347\240\201.md" +++ /dev/null @@ -1,68 +0,0 @@ -# 查看反编译源码 - -## 例子 -JavaBean.java -```java -public class JavaBean { - int value = 3; - final int value2 = 5; - static int value3 = 8; - static int value4 = -1; - String s = "买"; - - void getValue() { - int temp = 4; - } -} -``` -- 终端输入`javac JavaBean.java`得到JavaBean.class文件 -- 终端继续输入`javap -c JavaBean > JavaBean.txt` - -``` -Compiled from "JavaBean.java" -public class JavaBean { - int value; - - final int value2; - - static int value3; - - static int value4; - - java.lang.String s; - - public JavaBean(); - Code: - 0: aload_0 - 1: invokespecial #1 // Method java/lang/Object."":()V - 4: aload_0 - 5: iconst_3 - 6: putfield #2 // Field value:I - 9: aload_0 - 10: iconst_5 - 11: putfield #3 // Field value2:I - 14: aload_0 - 15: ldc #4 // String 买 - 17: putfield #5 // Field s:Ljava/lang/String; - 20: return - - void getValue(); - Code: - 0: iconst_4 - 1: istore_1 - 2: return - - static {}; - Code: - 0: bipush 8 - 2: putstatic #6 // Field value3:I - 5: iconst_m1 - 6: putstatic #7 // Field value4:I - 9: return -} -``` -暂时就不详细解释了。 - -当然如果查看更信息反编译文件,终端可以输入`javap -p JavaBean > JavaBean.txt` -内容我就不放了,它可以查看更多的字面量等,内容比较多。 - diff --git "a/Interview/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Interview/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index 829e98fa..00000000 --- "a/Interview/codes/jvm/\346\240\210\346\272\242\345\207\272\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,15 +0,0 @@ -# 模拟栈溢出的小例子 -> 启动参数: `-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError` -```java -public class Test { - - public void m() { - System.out.println("stack test overflow..."); - m(); - } - - public static void main(String[] args) { - new T1().m(); - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/mysql/InnoDB\345\222\214MyISAM.md" "b/Interview/codes/mysql/InnoDB\345\222\214MyISAM.md" deleted file mode 100644 index 2e0e9b92..00000000 --- "a/Interview/codes/mysql/InnoDB\345\222\214MyISAM.md" +++ /dev/null @@ -1,22 +0,0 @@ -# InnoDB和MyISAM - -## InnoDB -- 是 MySQL 默认的**事务型存储引擎**,只有在需要它不支持的特性时,才考虑使用其它存储引擎。 -- 实现了四个标准的隔离级别,默认级别是**可重复读(REPEATABLE READ)**。在可重复读隔离级别下,通过**多版本并发控制**(MVCC)+ **间隙锁**(Next-Key Locking)**防止幻影读**。 -- 主索引是**聚簇索引**,在**索引中保存了数据**,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的**可预测性读**、能够加快读操作并且自动创建的**自适应哈希索引**、能够加速插入操作的**插入缓冲区**等。 -- 支持真正的在**线热备份**。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -## MyISAM -- 设计简单,数据以**紧密格式存储**。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括**压缩表、空间数据索引**等。 -- **不支持事务**。 -- **不支持行级锁,只能对整张表加锁**,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -## 简单对比 -- **事务**: InnoDB 是事务型的,可以使用 `Commit` 和 `Rollback` 语句。 -- **并发**: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 -- **外键**: InnoDB 支持外键。 -- **备份**: InnoDB 支持在线热备份。 -- **崩溃恢复**: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 -- **其它特性**: MyISAM 支持压缩表和空间数据索引。 \ No newline at end of file diff --git "a/Interview/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" "b/Interview/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" deleted file mode 100644 index 672aea39..00000000 --- "a/Interview/codes/mysql/MVCC\347\232\204\347\274\272\347\202\271.md" +++ /dev/null @@ -1,8 +0,0 @@ -# MVCC的缺点 -> MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。 要知道的,MVCC机制下,会在更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本。 而undo log这个关键的东西,记载的内容是串行化的结果,记录了多个事务的过程,不属于多版本共存。 这么一看,似乎mysql的mvcc也并没有所谓的多版本共存 - -## InnoDB的实现 -1. 事务以**排他锁**的形式修改原始数据 -2. 把修改前的数据存放于**undo log**,通过回滚指针与主数据关联 -3. 修改成功(commit),数据放到**redo log**中,失败则恢复**undo log**中的数据(rollback) - diff --git "a/Interview/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" "b/Interview/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" deleted file mode 100644 index 92c0c654..00000000 --- "a/Interview/codes/mysql/MySQL\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\344\270\200\346\235\241SQL\350\257\255\345\217\245\347\232\204.md" +++ /dev/null @@ -1,20 +0,0 @@ -# MySQL是如何执行一条SQL的 - -先附上一张图: - -![执行过程图](http://media.dreamcat.ink/uPic/SQL%E6%89%A7%E8%A1%8C%E7%9A%84%E5%85%A8%E9%83%A8%E8%BF%87%E7%A8%8B.png) - -**MySQL内部可以分为服务层和存储引擎层两部分:** -1. **服务层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 -2. **存储引擎层负责数据的存储和提取**,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎。 - -**Server层按顺序执行sql的步骤为**: -客户端请求->**连接器**(验证用户身份,给予权限) -> **查询缓存**(存在缓存则直接返回,不存在则执行后续操作)->**分析器**(对SQL进行词法分析和语法分析操作) -> **优化器**(主要对执行的sql优化选择最优的执行方案方法) -> **执行器**(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口)->**去引擎层获取数据返回**(如果开启查询缓存则会缓存查询结果) - -简单概括: -- **连接器**:管理连接、权限验证; -- **查询缓存**:命中缓存则直接返回结果; -- **分析器**:对SQL进行词法分析、语法分析;(判断查询的SQL字段是否存在也是在这步) -- **优化器**:执行计划生成、选择索引; -- **执行器**:操作引擎、返回结果; -- **存储引擎**:存储数据、提供读写接口。 \ No newline at end of file diff --git "a/Interview/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" "b/Interview/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" deleted file mode 100644 index db848b0d..00000000 --- "a/Interview/codes/mysql/MySQL\347\232\204ACID\345\216\237\347\220\206.md" +++ /dev/null @@ -1,74 +0,0 @@ -# MySQL的ACID原理 - -> ACID嘛,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)! - -## 举例子 -我们以从A账户转账50元到B账户为例进行说明一下ACID,四大特性。 - -### 原子性 -> 根据定义,原子性是指一个事务是一个不可分割的工作单位,**其中的操作要么都做,要么都不做**。即要么转账成功,要么转账失败,是不存在中间的状态! - -**如果无法保证原子性会怎么样?** - -OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~ - -### 隔离性 -> 根据定义,隔离性是指**多个事务并发执行的时候,事务内部的操作与其他事务是隔离的**,并发执行的各个事务之间不能互相干扰。 - -**如果无法保证隔离性会怎么样?** - -OK,假设A账户有200元,B账户0元。A账户往B账户转账两次,金额为50元,分别在两个事务中执行。如果无法保证隔离性,会出现下面的情形 - - -![事务隔离](https://imgkr.cn-bj.ufileos.com/b3d04d1e-6bf1-4d49-b670-7e8a4801b385.png) - -如图所示,如果不保证隔离性,A扣款两次,而B只加款一次,凭空消失了50元,依然出现了数据不一致的情形! - -### 持久性 -> 根据定义,**持久性是指事务一旦提交,它对数据库的改变就应该是永久性的**。接下来的其他操作或故障不应该对其有任何影响。 - -**如果无法保证持久性会怎么样?** - -在MySQL中,为了解决CPU和磁盘速度不一致问题,MySQL是将磁盘上的数据加载到内存,对内存进行操作,然后再回写磁盘。好,假设此时宕机了,在内存中修改的数据全部丢失了,持久性就无法保证。 - -设想一下,系统提示你转账成功。但是你发现金额没有发生任何改变,此时数据出现了不合法的数据状态,我们将这种状态认为是**数据不一致**的情形。 - -### 一致性 -> 根据定义,一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 那什么是合法的数据状态呢? oK,这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。**满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!** - -**如果无法保证一致性会怎么样?** - -- 例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0。 -- 例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变。 - -## 如何保证 - -### 保证一致性 -OK,这个问题分为两个层面来说。 - -- **从数据库层面**,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。**数据库必须要实现AID三大特性,才有可能实现一致性**。例如,原子性无法保证,显然一致性也无法保证。 -- **从应用层面**,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据! - -### 保证原子性 -OK,是利用Innodb的**undo log**。 **undo log**名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。 例如 -- 当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据 -- 当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作 -- 当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作 - -**undo log**记录了这些回滚需要的信息,当事务执行失败或调用了**rollback**,导致事务需要回滚,便可以利用**undo log**中的信息将数据回滚到修改之前的样子。 - -### 保证持久性 -OK,是利用Innodb的**redo log**。 正如之前说的,MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题? -- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。 -- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。 - -于是,决定采用**redo log**解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在**redo log**中记录这次操作。当事务提交的时候,会将**redo log**日志进行刷盘(**redo log**一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据**undo log**和**binlog**内容决定回滚数据还是提交数据。 - -**采用redo log的好处?** - -其实好处就是将**redo log**进行刷盘比对数据页刷盘效率高,具体表现如下: -- **redo log**体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 -- **redo log**是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 - -### 保证隔离 -**利用的是锁和MVCC机制。** \ No newline at end of file diff --git "a/Interview/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" "b/Interview/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" deleted file mode 100644 index acd6d144..00000000 --- "a/Interview/codes/mysql/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" +++ /dev/null @@ -1,11 +0,0 @@ -# MySQL读写分离主从复制原理 - -主库(master)将变更写**binlog**日志,然后从库(slave)连接到主库之后,从库有一个**IO线程**,将主库的binlog日志**拷贝到自己本地**,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。 - -这里有一个非常重要的一点,就是从库同步主库数据的过程是**串行化**的,也就是说**主库上并行**的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。 - -而且这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。 - -所以mysql实际上在这一块有两个机制,一个是**半同步复制**,用来解决主库数据丢失问题;一个是**并行复制**,用来解决主从同步延时问题。 - -所谓并行复制,指的是从库**开启多个线程,并行读取relay log中不同库的日志**,然后并行重放不同库的日志,这是库级别的并行。 \ No newline at end of file diff --git "a/Interview/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" "b/Interview/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" deleted file mode 100644 index 7f910eab..00000000 --- "a/Interview/codes/mysql/\345\271\266\345\217\221\344\272\213\345\212\241\345\270\246\346\235\245\347\232\204\351\227\256\351\242\230.md" +++ /dev/null @@ -1,51 +0,0 @@ -# 并发事务带来的问题 - -## 脏读 - -![脏读](https://www.pdai.tech/_images/pics/dd782132-d830-4c55-9884-cfac0a541b8e.png) - -第一个事务首先读取var变量为50,接着准备更新为100的时,并未提交,第二个事务已经读取var为100,此时第一个事务做了回滚。最终第二个事务读取的var和数据库的var不一样。 - -## 丢弃修改 -![丢弃修改](https://www.pdai.tech/_images/pics/88ff46b3-028a-4dbb-a572-1f062b8b96d3.png) - -T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。例如:事务1读取某表中的数据A=50,事务2也读取A=50,事务1修改A=A+50,事务2也修改A=A+50,最终结果A=100,事务1的修改被丢失。 - -## 不可重复读 - -![不可重复读](https://www.pdai.tech/_images/pics/c8d18ca9-0b09-441a-9a0c-fb063630d708.png) - -T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 - -## 幻读 - -![幻读](https://www.pdai.tech/_images/pics/72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png) - -T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 - -## 不可重复读和幻读区别 -**不可重复读的重点是修改,幻读的重点在于新增或者删除。** - -- 例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。 -- 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记 录就变为了5条,这样就导致了幻读。 - -## 隔离级别 -- **未提交读**,事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** -- **提交读**,对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **重复读**,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生** -- **可串行化读**,在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读** - -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| ---------------- | ---- | ---------- | ------ | -| READ-UNCOMMITTED | √ | √ | √ | -| READ-COMMITTED | × | √ | √ | -| REPEATABLE-READ | × | × | √ | -| SERIALIZABLE | × | × | × | - -MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ**(可重读) - -> **这里需要注意的是**:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是**Next-Key Lock 锁**算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 - -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失**。 - -InnoDB 存储引擎在分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。 \ No newline at end of file diff --git "a/Interview/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" "b/Interview/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" deleted file mode 100644 index 0649118a..00000000 --- "a/Interview/codes/mysql/\346\225\260\346\215\256\345\272\223\347\273\223\346\236\204\344\274\230\345\214\226.md" +++ /dev/null @@ -1,26 +0,0 @@ -# 数据库结构优化 -- **范式优化**: 比如消除冗余(节省空间。。) -- **反范式优化**:比如适当加冗余等(减少join) -- **限定数据的范围**: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- **读/写分离**: 经典的数据库拆分方案,主库负责写,从库负责读; -- **拆分表**:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 - -## 拆分表 -**拆分其实又分垂直拆分和水平拆分**: - -举例子: - -简单购物系统暂设涉及如下表: -1. 产品表(数据量10w,稳定) -2. 订单表(数据量200w,且有增长趋势) -3. 用户表 (数据量100w,且有增长趋势) - -### 垂直拆分 -- 解决问题:表与表之间的io竞争 -- 不解决问题:单表中数据量增长出现的压力 -- 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 - -### 水平拆分 -- 解决问题:单表中数据量增长出现的压力 -- 不解决问题:表与表之间的io争夺 -- 方案:**用户表**通过性别拆分为**男用户表**和**女用户表**,**订单表**通过已完成和完成中拆分为**已完成订单**和**未完成订单**,**产品表未完成订单放一个server上**,**已完成订单表盒男用户表放一个server上**,**女用户表放一个server上**(女的爱购物 哈哈)。 \ No newline at end of file diff --git "a/Interview/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" "b/Interview/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" deleted file mode 100644 index 6a3b7eb6..00000000 --- "a/Interview/codes/mysql/\346\225\260\346\215\256\345\272\223\350\214\203\345\274\217.md" +++ /dev/null @@ -1,10 +0,0 @@ -# 数据库范式 - -## 第一范式 -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 - -## 第二范式 -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是**非主属性非部分依赖于主关键字**。 - -## 第三范式 -满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余)**外键** \ No newline at end of file diff --git "a/Interview/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" "b/Interview/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" deleted file mode 100644 index f03a8639..00000000 --- "a/Interview/codes/mysql/\347\264\242\345\274\225\347\247\215\347\261\273.md" +++ /dev/null @@ -1,30 +0,0 @@ -# 索引种类 - -## 索引类型 -- **FULLTEXT** :即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- **HASH** :由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- **BTREE** :BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- **RTREE** :RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 相对于BTREE,RTREE的优势在于范围查找。 - -## 索引种类 -- **普通索引**:仅加速查询 -- **唯一索引**:加速查询 + 列值唯一(可以有null) -- **主键索引**:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 -- **组合索引**:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- **全文索引**:对文本的内容进行分词,进行搜索 -- **索引合并**:使用多个单列索引组合搜索 -- **覆盖索引**:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 -- **聚簇索引**:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容) - - -## 为什么使用索引 -- 通过创建唯一性索引,可以保证数据库表中每一行数据的**唯一性**。 -- 可以大大加快数据的**检索速度**,这也是创建索引的最主要的原因。 -- 帮助服务器**避免排序和临时表**。 -- 将**随机IO变为顺序IO**。 -- 可以**加速表和表之间的连接**,特别是在实现数据的参考完整性方面特别有意义。 - -## 索引缺点 -- 当对表中的数据进行增加、删除和修改的时候,**索引也要动态的维护**,这样就降低了数据的维护速度。 -- 索引需要**占物理空间**,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 -- **创建索引和维护索引要耗费时间**,这种时间随着数据量的增加而增加 \ No newline at end of file diff --git "a/Interview/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" "b/Interview/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" deleted file mode 100644 index 4282598a..00000000 --- "a/Interview/codes/mysql/\347\272\242\351\273\221\346\240\221\343\200\201B\346\240\221\345\222\214B+\346\240\221.md" +++ /dev/null @@ -1,43 +0,0 @@ -# 红黑树、B树和B+树 - -## 红黑树 -> 红黑树也是一种自平衡的二叉查找树。 - -![红黑树](https://www.pdai.tech/_images/alg/alg-tree-14.png) - -### 特点 -- 每个结点要么是红的要么是黑的。(红或黑) -- 根结点是黑的。 (根黑) -- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。 (叶黑) -- 如果一个结点是红的,那么它的两个儿子都是黑的。 (红子黑) -- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。(路径下黑相同) - -### 适用场景 -- Java `ConcurrentHashMap` & `TreeMap` -- C++ STL: map & set -- linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块 -- epoll在内核中的实现,用红黑树管理事件块 -- nginx中,用红黑树管理timer等 - -## B树 -> B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让**查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成**。B树,概括来说是一个**一般化的二叉查找树**(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。其中,概念较为复杂,给个简单的图理解: - - -![简单B树](https://imgkr.cn-bj.ufileos.com/e30bb7ee-f0ec-4fd6-a4f2-8f82405dfe9a.png) - - - -## B+树 -- 在B树基础上,为**叶子结点增加链表指针**(B树+叶子有序链表),所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -- B+树的**非叶子节点不保存数据**,只保存**子树的临界值**(最大或者最小),所以同样大小的节点,**B+树相对于B树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 - -给个图: - - -![B+](https://imgkr.cn-bj.ufileos.com/0dd56365-0cc0-4f95-973b-5465c9664f71.png) - - -## 为什么用B+树而不用hash和B-Tree -- 利用Hash需要把数据全部**加载到内存中**,如果数据量大,是一件很**消耗内存**的事,而采用B+树,是基于**按照节点分段加载,由此减少内存消耗**。 -- 和业务场景有段,**对于唯一查找**(查找一个值),Hash确实更快,**但数据库中经常查询多条数据**,这时候由于B+数据的有序性,与叶子节点又有链表相连,他的查询效率会比Hash快的多。 -- b+树的**非叶子节点不保存数据**,**只保存子树的临界值**(最大或者最小),所以同样大小的节点,**b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少**。 \ No newline at end of file diff --git "a/Interview/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" "b/Interview/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" deleted file mode 100644 index a6437463..00000000 --- "a/Interview/codes/other/Sentinel\347\233\270\345\205\263\346\246\202\345\277\265.md" +++ /dev/null @@ -1,24 +0,0 @@ -# Sentinel? -> Sentinel是一个面试分布式架构的轻量级服务保护框架,主要以流量控制、熔断降级、系统负载保护等多个维度。 - -## 服务限流 -> 当系统资源不够,不足以应对大量请求,对系统按照预设的规则进行流量限制或功能限制 - -## 服务熔断 -> 当调用目标服务的请求和调用大量超时或失败,服务调用方为避免造成长时间的阻塞造成影响其他服务,后续对该服务接口的调用不再经过进行请求,直接执行本地的默认方法 - -## 服务降级 -> 为了保证核心业务在大量请求下能正常运行,根据实际业务情况及流量,对部分服务降低优先级,有策略的不处理或用简单的方式处理 - -## 为什么熔断降级 -> 系统承载的访问量是有限的,如果不做流量控制,会导致系统资源占满,服务超时,从而所有用户无法使用,通过服务限流控制请求的量,服务降级省掉非核心业务对系统资源的占用,最大化利用系统资源,尽可能服务更多用户 - -## 工作原理 -![](https://www.javazhiyin.com/wp-content/uploads/2019/10/java6-1572258892.png) - -## 对比 -![](https://imgkr.cn-bj.ufileos.com/8f2cf909-4c41-47c3-89d9-d6eb2676a519.png) - -**值得补充的是**:相比 Hystrix 基于线程池隔离进行限流,这种方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。 - -Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离 diff --git "a/Interview/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" "b/Interview/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" deleted file mode 100644 index c8a69ffd..00000000 --- "a/Interview/codes/redis/Redis\345\201\232\345\273\266\350\277\237\351\230\237\345\210\227.md" +++ /dev/null @@ -1,218 +0,0 @@ -# Redis做延迟队列 - -## 引言 -> 班车预约平台,订单自动取消任务,采用的是监听Redis的key键消失的策略,属于被动轮询。而用Redis做延迟队列,需要主动轮询。班车平台的github:[https://github.com/DreamCats/school-bus](https://github.com/DreamCats/school-bus) - -## 实现思路 -1. 将整个Redis当做消息池,以kv形式存储消息,key为id,value为具体的消息body -2. 使用ZSET做优先队列,按照score维持优先级(用当前时间+需要延时的时间作为score) -3. 轮询ZSET,拿出score比当前时间戳大的数据(已过期的) -4. 根据id拿到消息池的具体消息进行消费 -5. 消费成功,删除改队列和消息 -6. 消费失败,让该消息重新回到队列 - - -## 代码实现 - -### RedisMessage - -```java -public class RedisMessage { - - /** - * 消息id - */ - private String id; - - /** - * 消息延迟/毫秒 - */ - private long delay; - - /** - * 消息存活时间 - */ - private int ttl; - - /** - * 消息体,对应业务内容 - */ - private String body; - - /** - * 创建时间,如果只有优先级没有延迟,可以设置创建时间为0 - * 用来消除时间的影响 - */ - private long createTime; - -} -``` - -### RedisMQ队列 -```java -@Component -public class RedisMQ { - - /** - * 消息池前缀,以此前缀加上传递的消息id作为key,以消息{@link RedisMessage} - * 的消息体body作为值存储 - */ - public static final String MSG_POOL = "Message:Pool:"; - /** - * zset队列 名称 queue - */ - public static final String QUEUE_NAME = "Message:Queue:"; - - private static final int SEMIH = 30*60; - - @Autowired - private RedisUtils redisUtils; - - /** - * 存入消息池 - * @param message - * @return - */ - public boolean addMsgPool(RedisMessage message) { - - if (null != message) { - return redisUtils.set(MSG_POOL + message.getId(), message.getBody(), Long.valueOf(message.getTtl() + SEMIH)); - } - return false; - } - - /** - * 从消息池中删除消息 - * @param id - */ - public void deMsgPool(String id) { - redisUtils.del(MSG_POOL + id); - } - - /** - * 向队列中添加消息 - * @param key - * @param score - * @param val - */ - public void enMessage(String key, long score, String val) { - redisUtils.zsset(key, val, score); - } - - /** - * 从队列额删除消息 - * @param key - * @param id - * @return - */ - public boolean deMessage(String key, String id) { - return redisUtils.zdel(key, id); - } -} -``` - -### Redis的工具类 -可以去[github地址](https://github.com/DreamCats/school-bus/blob/master/school-bus/guns-order/src/test/java/com/stylefeng/guns/rest/RedisUtils.java) - -### 生产者(用户) -```java -@Component -public class MessageProvider { - - private static int delay = 30;//30秒,可自己动态传入 - - @Resource - private RedisMQ redisMQ; - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - //改造成redis - public void sendMessage(String messageContent) { - try { - if (messageContent != null){ - String seqId = UUID.randomUUID().toString(); - // 将有效信息放入消息队列和消息池中 - RedisMessage message = new RedisMessage(); - // 可以添加延迟配置 - message.setDelay(delay*1000); - message.setCreateTime(System.currentTimeMillis()); - message.setBody(messageContent); - message.setId(seqId); - // 设置消息池ttl,防止长期占用 - message.setTtl(delay + 360); - redisMQ.addMsgPool(message); - //当前时间加上延时的时间,作为score - Long delayTime = message.getCreateTime() + message.getDelay(); - String d = sdf.format(message.getCreateTime()); - System.out.println("当前时间:" + d+",消费的时间:" + sdf.format(delayTime)); - redisMQ.enMessage(RedisMQ.QUEUE_NAME,delayTime, message.getId()); - }else { - System.out.println("消息内容为空"); - } - }catch (Exception e){ - e.printStackTrace(); - } - } -} -``` - -### 消费者 -```java -@Component -public class RedisMQConsumer { - - @Resource - private RedisMQ redisMQ; - - @Autowired - private RedisUtils redisUtils; - - @Autowired - private MessageProvider provider; - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - - /** - * 消息队列监听器 - * 也可以开个线程池 - * 主动轮询 - */ - @Scheduled(cron = "*/1 * * * * *") - public void monitor() { - // 取出0到当前时间的权重值 - Set set = redisUtils.rangeByScore(RedisMQ.QUEUE_NAME, 0, System.currentTimeMillis()); - if (null != set) { - // 如果不为空 - // 获取当前时间 - long current = System.currentTimeMillis(); - for (Object id : set) { - long score = redisUtils.getScore(RedisMQ.QUEUE_NAME, (String) id).longValue(); - if (current >= score) { - // 已超时的消息拿出来消费 - String str = ""; - try { - // 根据id取出消息 - str = (String) redisUtils.get(RedisMQ.MSG_POOL + id); - System.out.println("消费了:" + str+ ",消费的时间:" + sdf.format(System.currentTimeMillis())); - } catch (Exception e) { - e.printStackTrace(); - //如果取消息发生了异常,则将消息重新放回队列 - System.out.println("消费异常,重新回到队列"); - provider.sendMessage(str); - } finally { - // 不管消费成功与非,都要删除当前消息 - redisMQ.deMessage(RedisMQ.QUEUE_NAME, (String) id); - redisMQ.deMsgPool((String) id); - } - } - } - } - } -} -``` - -### 结合班车预约平台思路 -1. 当前端发一个带延迟时间戳的请求,我们后端可以在zset当做延迟队列,订单ID为元素的key,时间戳为当前时间+延迟时间戳当做score -2. 可以使用Springboot定时器或者线程池延迟主动轮询拉取队列中符合条件的score,根据符合条件score的订单id -3. 根据符合条件的订单id,进行回退座位。 \ No newline at end of file diff --git "a/Interview/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" "b/Interview/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" deleted file mode 100644 index dec79def..00000000 --- "a/Interview/codes/redis/\346\267\261\347\251\266Redis\347\232\204\345\272\225\345\261\202\347\273\223\346\236\204.md" +++ /dev/null @@ -1,185 +0,0 @@ -# 深究Redis的底层结构 - -## 简单动态字符串(SDS) -> Redis没有直接使用C语言传统的字符串(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串(Simple dynamic string,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。 - -### 看一下SDS的定义 -```c -struct sdshdr { - // 记录buf数组中已使用字节的数量 - // 等于sds所保存字符串的长度 - int len; - - // 记录buf数组中未使用字节的数量 - int free; - - // 字节数组,用于保存字符串 - char buf[]; -} -``` - -### 有何优点 -1. 获取字符串长度的复杂度为O(1)。 -2. 杜绝缓冲区溢出。 -3. 减少修改字符串长度时所需要的内存重分配次数。 -4. 二进制安全。 -5. 兼容部分C字符串函数。 - -## 链表 -> 毫无疑问,Redis也不会不适用链表的,毕竟链表这么好的结构,用起来很香的。当有一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的额字符串时,Redis就会使用链表作为列表建的底层实现。 - -### 节点底层结构 -```c -typedef struct listNode { - // 前置节点 - struct listNode *prev; - // 后置节点 - struct listNode *next; - // 节点的值 - void *value; -} listNode; -``` -和其他的节点结构没啥区别... - -### list底层结构 -```c -typedef struct list { - // 表头节点 - listNode *head; - // 表尾节点 - listNode *tail; - // 链表所包含的节点数量 - unsigned long len; - // 节点值复制函数 - void *(*dup)(void *ptr); - // 节点值是放过函数 - void (*free)(void *ptr); - // 节点值对比函数 - int(*match)(void *ptr, void *key); -} list; -``` - -### 特性 -1. 链表被广泛用于实现Redis的各种功能,比如列表建、发布与订阅、慢查询、监视器等。 -2. 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。 -3. 每个链表使用一个list结构表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。 -4. 因为链表表头的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。 -5. 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。 - -## 字典 -> 说白了, 字典就是键值对。Redis的字典的底层就是哈希表而已。 - -### 哈希表 -```c -typedef struct dictht { - // 哈希表数组 - dictEntry **table; - // 哈希表大小 - unsigned long size; - // 哈希表大小掩码,用于计算索引值 - // 总是等于size-1 - unsigned long sizemark; - // 该哈希表已有节点的数量 - unsigned long used; -} dichht; -``` - -那么,哈希表数组的结构table呢? - -### dictEntry的结构 -```c -typedef struct dictEntry { - // 键 - void *key; - // 值 - union { - void *val; - uint64_t u64; - int64_t s64; - } v; - // 指向下个哈希表节点,形成链表 - struct dictEntry *nect; -} dictEntry; -``` -dict的底层结构省略。 - -### 哈希算法 -当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash算法。这种算法的优点在于即使输入的键是规律的,算法仍能给出一个个很好的随机分布性,并且算法的计算速度非常快。 - -### 哈希冲突 -Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用这个单向链表连接起来,这就解决了键冲突的问题。 - -### 特性 -1. 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。 -2. Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。 -3. Redis使用MurmurHash2算法来计算键的哈希值。 -4. 哈希表使用链地址法来解决键冲突。 - -## 跳跃表 - -先看这样一张图: -![](https://user-gold-cdn.xitu.io/2019/4/18/16a30ca4a17c6280?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -如上图,我们要查找一个元素,就需要从头节点开始遍历,直到找到对应的节点或者是第一个大于要查找的元素的节点(没找到)。时间复杂度为O(N)。 - -这个查找效率是比较低的,但如果我们把列表的某些节点拔高一层,如下图,例如把每两个节点中有一个节点变成两层。那么第二层的节点只有第一层的一半,查找效率也就会提高。 - -![](https://user-gold-cdn.xitu.io/2019/4/18/16a30ca4aa58e4bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) - -查找的步骤是从头节点的顶层开始,查到第一个大于指定元素的节点时,退回上一节点,在下一层继续查找。 - -比如我们要查找16: -1. 从头节点的最顶层开始,先到节点7。 -2. 7的下一个节点是39,大于16,因此我们退回到7 -3. 从7开始,在下一层继续查找,就可以找到16。 - -这个例子中遍历的节点不比一维列表少,但是当节点更多,查找的数字更大时,这种做法的优势就体现出来了。还是上面的例子,如果我们要**查找的是39**,那么只需要访问两个节点(7、39)就可以找到了。这比一维列表要减少一半的数量。 - -为了避免插入操作的时间复杂度是O(N),skiplist每层的数量不会严格按照2:1的比例,而是对每个要插入的元素随机一个层数。 - -随机层数的计算过程如下: -1. 每个节点都有第一层 -2. 那么它有第二层的概率是p,有第三层的概率是p*p -3. 不能超过最大层数 - -### zskiplistNode -```c -typedef struct zskiplistNode { - // 后退指针 - struct zskiplistNode *backward; - // 分值 权重 - double score; - // 成员对象 - robj *obj; - // 层 - struct zskiplistLevel { - // 前进指针 - struct zskiplistNode *forward; - // 跨度 - unsigned int span; - } leval[]; -} zskiplistNode; -``` -一般来说,层的数量越多,访问其他节点的速度越快。 - -### zskipList -```c -typedef struct zskiplist { - // 表头节点和表尾节点 - struct zskiplistNode *header, *tail; - // 表中节点的数量 - unsigned long length; - // 表中层数最大的节点的层数 - int leval; -} zskiplist; -``` - -### 特性 -1. 跳跃表是有序集合的底层实现之一 -2. Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点 -3. 每个跳跃表节点的层高都是1至32之间的随机数 -4. 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。 -5. 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。 - -## 压缩列表 -> 一看名字,就是为了节省内存造的列表结构。压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。 \ No newline at end of file diff --git "a/Interview/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" "b/Interview/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" deleted file mode 100644 index 284133a7..00000000 --- "a/Interview/codes/spring/\346\211\213\345\206\231SpringAOP\344\273\243\347\240\201.md" +++ /dev/null @@ -1,116 +0,0 @@ -# 手写SpringAOP - -## 定义advice接口 - -```java -public interface Advice extends InvocationHandler { -} -``` - -## 定义方法回调接口 -```java -public interface MethodInvocation { - void invoke(); -} -``` - -## 定义前置通知 -```java -public class BeforeAdvice implements Advice { - - private Object bean; // bean对象 - - private MethodInvocation methodInvocation; // 方法调用 - - public BeforeAdvice(Object bean, MethodInvocation methodInvocation) { - this.bean = bean; - this.methodInvocation = methodInvocation; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // 在目标方法前调用,前置 - methodInvocation.invoke(); - return method.invoke(bean, args); - } -} -``` - -## 定义后置通知 -```java -public class AfterAdvice implements Advice { - - private Object bean; - - private MethodInvocation methodInvocation; - - public AfterAdvice(Object bean, MethodInvocation methodInvocation) { - this.bean = bean; - this.methodInvocation = methodInvocation; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // 业务逻辑调用 - Object invoke = method.invoke(bean, args); - // 后置通知调用 - methodInvocation.invoke(); - return invoke; - } -} -``` - -## 定义目标方法接口 -```java -public interface HelloService { - void sayHelloWorld(); -} -``` - -## 实现目标方法 -```java -public class HelloServiceImpl implements HelloService { - - @Override - public void sayHelloWorld() { - System.out.println("hello world..."); - } -} -``` - -## SpringAOP的实现 -```java -public class SimpleAOP { - // 反射代理 - public static Object getProxy(Object bean, Advice advice) { - return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader() - , bean.getClass().getInterfaces(), advice); - } -} -``` - -## 简单测试 -```java -public class SimpleAOPTest { - public static void main(String[] args) { - // 1. 创建一个 MethodInvocation 实现类 切面逻辑类 - MethodInvocation logTask = () -> System.out.println("log task start"); - MethodInvocation logTaskEnd = () -> System.out.println("log task end"); - - // 业务逻辑类 - HelloServiceImpl helloService = new HelloServiceImpl(); - - // 2. 创建一个Advice 切入点 - BeforeAdvice beforeAdvice = new BeforeAdvice(helloService, logTask); - AfterAdvice afterAdvice = new AfterAdvice(helloService, logTaskEnd); - - // 3. 为目标对象生成代理 - HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloService, beforeAdvice); - HelloService helloServiceImplProxyAfter = (HelloService) SimpleAOP.getProxy(helloService, afterAdvice); - - helloServiceImplProxy.sayHelloWorld(); - helloServiceImplProxyAfter.sayHelloWorld(); - - } -} -``` diff --git "a/Interview/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index a731c1bd..00000000 --- "a/Interview/codes/thread/AQS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,318 +0,0 @@ -# AQS -> Java的`AbstractQueuedSynchronizer`抽象类,抽象同队队列 - - -## 原理 -> AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。 -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` -状态信息通过 protected 类型的`getState`,`setState`,`compareAndSetState`进行操作 -```java -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} -``` - -**AQS 定义两种资源共享方式** - -1. **Exclusive**(独占)只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁。 - - 总结:公平锁和非公平锁只有两处不同: - - 1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。 - 2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。 - -2. **Share**(共享)多个线程可同时执行,如 Semaphore/CountDownLatch。Semaphore、 CyclicBarrier、ReadWriteLock 。 - -**AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:** -```java -isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 -tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 -tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 -tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 -tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 -``` - -## ReentrantLock -> 我们知道,我们需要在哪里上锁,一般都是调用`xxx.lock()`,在finally的代码块中释放锁`xxx.unlock()`,那么我们来看一下源码。 - -### 构造方法 -```java -// 默认是非公平锁 -public ReentrantLock() { - sync = new NonfairSync(); -} -// 可选参数,是否公平 -public ReentrantLock(boolean fair) { - sync = fair ? new FairSync() : new NonfairSync(); -} -``` - -#### 公平锁的实现 -```java -protected final boolean tryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (!hasQueuedPredecessors() && // 判断队列是否轮到该线程 - compareAndSetState(0, acquires)) { // 利用cas更换状态 - setExclusiveOwnerThread(current); // 如果都ok,就设置独占锁 - return true; - } - } - else if (current == getExclusiveOwnerThread()) {// 判断当前独占锁是否还是当前线程 - int nextc = c + acquires;// 状态累加 - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); - setState(nextc); // 设置状态 - return true; - } // 否则false - return false; -} -``` -#### 非公平锁的实现 -> 比公平锁少了队列的判断而已。 -```java -final boolean nonfairTryAcquire(int acquires) { - final Thread current = Thread.currentThread(); - int c = getState(); - if (c == 0) { - if (compareAndSetState(0, acquires)) {// 在这里... - setExclusiveOwnerThread(current); - return true; - } - } - else if (current == getExclusiveOwnerThread()) { - int nextc = c + acquires; - if (nextc < 0) // overflow - throw new Error("Maximum lock count exceeded"); - setState(nextc); - return true; - } - return false; -} -``` - -## CountDownLatch -CountDownLatch是共享锁的一种实现,它默认构造 `AQS` 的 `state` 值为 `count`。当线程使用`countDown`方法时,其实使用了`tryReleaseShared`方法以CAS的操作来减少`state`,直至`state`为0就代表所有的线程都调用了`countDown`方法。当调用`await`方法的时候,如果`state`不为0,就代表仍然有线程没有调用countDown方法,那么就把已经调用过countDown的线程都放入**阻塞队列Park,并自旋CAS判断state == 0**,直至最后一个线程调用了countDown,使得state == 0,于是阻塞的线程便判断成功,全部往下执行。 - -### 构造方法 -```java -public CountDownLatch(int count) { - if (count < 0) throw new IllegalArgumentException("count < 0"); - this.sync = new Sync(count); // 这里设置state的次数 -} -``` - -### countDown -```java -public void countDown() { - sync.releaseShared(1); -} -``` -### tryReleaseShared -```java -protected boolean tryReleaseShared(int releases) { - // Decrement count; signal when transition to zero - for (;;) { - int c = getState(); - if (c == 0) - return false; - int nextc = c-1; // 每执行一次该方法,状态减一 - if (compareAndSetState(c, nextc)) - return nextc == 0; - } -} -``` - -### 三种用法: - -1. 某一线程在开始运行前等待 n 个线程执行完毕。将 CountDownLatch 的计数器初始化为 n :`new CountDownLatch(n)`,每当一个任务线程执行完毕,就将计数器减 1 `countdownlatch.countDown()`,当计数器的值变为 0 时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 -2. 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1)`,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。 -3. 死锁检测:一个非常方便的使用场景是,你可以使用 n 个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 - -### 举个例子 -> 主线程等待多个子线程 -```java -public class CountDownLatchDemo { - - public static void main(String[] args) throws InterruptedException { - countDownLatchTest(); -// general(); - } - - public static void general() { - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - }, "Thread --> " + i).start(); - } - while (Thread.activeCount() > 2) { // 一般方式 - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } - } - - public static void countDownLatchTest() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(6); - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - countDownLatch.countDown(); // 每个线程执行完都会调用countDown - }, "Thread --> " + i).start(); - } - countDownLatch.await(); // 阻塞主线程 - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } -} -``` - -## CyclicBarrier -> CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier 默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - -### 举个例子 -```java -public class CyclicBarrierDemo { - public static void main(String[] args) { - cyclicBarrierTest(); - } - - public static void cyclicBarrierTest() { - CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { - System.out.println("====召唤神龙===="); // 拦截到一定数量,执行该代码 - }); - for (int i = 0; i < 7; i++) { - final int tempInt = i; - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 收集到第" + tempInt + "颗龙珠"); - try { - cyclicBarrier.await(); // 拦截 - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (BrokenBarrierException e) { - e.printStackTrace(); - } - }, "" + i).start(); - } - } -} -``` -当调用 `CyclicBarrier` 对象调用 `await()` 方法时,实际上调用的是`dowait(false, 0L)`方法。 `await()` 方法就像树立起一个栅栏的行为一样,将线程挡住了,当拦住的线程数量达到 parties 的值时,栅栏才会打开,线程才得以通过执行。 -看一下源码: -```java - // 当线程数量或者请求数量达到 count 时 await 之后的方法才会被执行。上面的示例中 count 的值就为 7。 - private int count; - /** - * Main barrier code, covering the various policies. - */ - private int dowait(boolean timed, long nanos) - throws InterruptedException, BrokenBarrierException, - TimeoutException { - final ReentrantLock lock = this.lock; - // 锁住 - lock.lock(); - try { - final Generation g = generation; - - if (g.broken) - throw new BrokenBarrierException(); - - // 如果线程中断了,抛出异常 - if (Thread.interrupted()) { - breakBarrier(); - throw new InterruptedException(); - } - // cout减1 - int index = --count; - // 当 count 数量减为 0 之后说明最后一个线程已经到达栅栏了,也就是达到了可以执行await 方法之后的条件 - if (index == 0) { // tripped - boolean ranAction = false; - try { - final Runnable command = barrierCommand; - if (command != null) - command.run(); - ranAction = true; - // 将 count 重置为 parties 属性的初始化值 - // 唤醒之前等待的线程 - // 下一波执行开始 - nextGeneration(); - return 0; - } finally { - if (!ranAction) - breakBarrier(); - } - } - - // loop until tripped, broken, interrupted, or timed out - for (;;) { - try { - if (!timed) - trip.await(); - else if (nanos > 0L) - nanos = trip.awaitNanos(nanos); - } catch (InterruptedException ie) { - if (g == generation && ! g.broken) { - breakBarrier(); - throw ie; - } else { - // We're about to finish waiting even if we had not - // been interrupted, so this interrupt is deemed to - // "belong" to subsequent execution. - Thread.currentThread().interrupt(); - } - } - - if (g.broken) - throw new BrokenBarrierException(); - - if (g != generation) - return index; - - if (timed && nanos <= 0L) { - breakBarrier(); - throw new TimeoutException(); - } - } - } finally { - lock.unlock(); - } - } -``` -总结:`CyclicBarrier` 内部通过一个 count 变量作为计数器,count 的初始值为 parties 属性的初始化值,每当一个线程到了栅栏这里了,那么就将计数器减一。如果 count 值为 0 了,表示这是这一代最后一个线程到达栅栏,就尝试执行我们构造方法中输入的任务。 - -## Semaphore -> **synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** - -### 举个例子 -```java -public class SemaphoreDemo { - public static void main(String[] args) { - Semaphore semaphore = new Semaphore(3);// 模拟三个停车位 - for (int i = 0; i < 6; i++) { // 模拟6部汽车 - new Thread(() -> { - try { - semaphore.acquire(); - System.out.println(Thread.currentThread().getName() + " 抢到车位"); - // 停车3s - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " 停车3s后离开车位"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - semaphore.release(); - } - }, "Car " + i).start(); - } - } -} -``` diff --git "a/Interview/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index c01f3e17..00000000 --- "a/Interview/codes/thread/CAS\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,89 +0,0 @@ -# CAS -> **我们在读Concurrent包下的类的源码时,发现无论是**ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了`CAS` - -```java -public class Test { - - public AtomicInteger i; - - public void add() { - i.getAndIncrement(); - } -} -``` -**我们来看`getAndIncrement`的内部:** -```java -public final int getAndIncrement() { - return unsafe.getAndAddInt(this, valueOffset, 1); -} -``` -**再深入到`getAndAddInt`():** -```java -public final int getAndAddInt(Object var1, long var2, int var4) { - int var5; - do { - var5 = this.getIntVolatile(var1, var2); - } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); - - return var5; -} -``` -**现在重点来了,`compareAndSwapInt(var1, var2, var5, var5 + var4)`其实换成`compareAndSwapInt(obj, offset, expect, update)`比较清楚,意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。** - -```c -UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) - UnsafeWrapper("Unsafe_CompareAndSwapInt"); - oop p = JNIHandles::resolve(obj); - jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); - return (jint)(Atomic::cmpxchg(x, addr, e)) == e; -UNSAFE_END -``` -**p是取出的对象,addr是p中offset处的地址,最后调用了`Atomic::cmpxchg(x, addr, e)`, 其中参数x是即将更新的值,参数e是原内存的值。代码中能看到cmpxchg有基于各个平台的实现。** - -## ABA问题 -> 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。 - -**目前在JDK的atomic包里提供了一个类`AtomicStampedReference`来解决ABA问题。** - -```java -public class ABADemo { - static AtomicInteger atomicInteger = new AtomicInteger(100); - static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1); - - public static void main(String[] args) { - System.out.println("=====ABA的问题产生====="); - new Thread(() -> { - atomicInteger.compareAndSet(100, 101); - atomicInteger.compareAndSet(101, 100); - }, "t1").start(); - - new Thread(() -> { - // 保证线程1完成一次ABA问题 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(atomicInteger.compareAndSet(100, 2020) + " " + atomicInteger.get()); - }, "t2").start(); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - - System.out.println("=====解决ABA的问题====="); - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); // 第一次获取版本号 - System.out.println(Thread.currentThread().getName() + " 第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); - atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp()); - }, "t3").start(); - - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); - System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } - boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1); - System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp()); - System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference()); - }, "t4").start(); - - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" "b/Interview/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" deleted file mode 100644 index e8c3f0a3..00000000 --- "a/Interview/codes/thread/Java\351\224\201\347\232\204\344\273\213\347\273\215.md" +++ /dev/null @@ -1,98 +0,0 @@ -# 各种锁 -## 公平锁/非公平锁 -**公平锁指多个线程按照申请锁的顺序来获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象(很长时间都没获取到锁-非洲人...),ReentrantLock,了解一下。** - -## 可重入锁 -**可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,典型的synchronized,了解一下** -```java -synchronized void setA() throws Exception { - Thread.sleep(1000); - setB(); // 因为获取了setA()的锁,此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行 -} -synchronized void setB() throws Exception { - Thread.sleep(1000); -} -``` - -## 独享锁/共享锁 -- 独享锁:是指该锁一次只能被一个线程所持有。 -- 共享锁:是该锁可被多个线程所持有。 - -## 互斥锁/读写锁 -**上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是其具体的实现** - -## 乐观锁/悲观锁 - -1. **乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待兵法同步的角度。** -2. **悲观锁认为对于同一个人数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。** -3. **乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作时没有事情的。** -4. **悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁带来大量的性能提升。** -5. **悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子类操作的更新。重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁** - -## 分段锁 - -1. **分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。** -2. **以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** -3. **当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** -4. **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** - -## 偏向锁/轻量级锁/重量级锁 - -1. **这三种锁是锁的状态,并且是针对Synchronized。在Java5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。** -2. **偏向锁的适用场景:始终只有一个线程在执行代码块,在它没有执行完释放锁之前,没有其它线程去执行同步快,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;在有锁竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。** -3. **轻量级锁是指当锁是偏向锁的时候,被另一个线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自选的形式尝试获取锁,不会阻塞,提高性能。** -4. **重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。** - -## 自旋锁 - -1. **在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。** -2. **自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。** -3. **自旋锁尽可能的减少线程的阻塞,适用于锁的竞争不激烈,且占用锁时间非常短的代码来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗。** -4. **但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适用使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。** - -### 手写自旋锁的例子 -```java -public class SpinLock { - - // 原子引用线程 - AtomicReference atomicReference = new AtomicReference<>(); - - public void mylock() { - Thread thread = Thread.currentThread(); - System.out.println(Thread.currentThread().getName() + " como in..."); - while (!atomicReference.compareAndSet(null, thread)) { -// System.out.println("不爽,重新获取一次值瞧瞧..."); - } - } - - public void myUnlock() { - Thread thread = Thread.currentThread(); - atomicReference.compareAndSet(thread, null); - System.out.println(Thread.currentThread().getName() + " invoke myUnLock..."); - } - - public static void main(String[] args) { - SpinLock spinLock = new SpinLock(); - new Thread(() -> { - spinLock.mylock(); - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - spinLock.myUnlock(); - }, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - - new Thread(() -> { - spinLock.mylock(); - spinLock.myUnlock(); - }, "t2").start(); - } - -} -``` - -## Java锁总结 - -**Java锁机制可归为Sychornized锁和Lock锁两类。Synchronized是基于JVM来保证数据同步的,而Lock则是硬件层面,依赖特殊的CPU指令来实现数据同步的。** - -- Synchronized是一个非公平、悲观、独享、互斥、可重入的重量级锁。 -- ReentrantLock是一个默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁。 -- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 \ No newline at end of file diff --git "a/Interview/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" "b/Interview/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" deleted file mode 100644 index d140c65c..00000000 --- "a/Interview/codes/thread/Runnable\343\200\201Callable\345\222\214FutureTask.md" +++ /dev/null @@ -1,61 +0,0 @@ -# Runnable、Callable和FutureTask -> Runnable自Java 1.0以来一直存在,但Callable仅在Java 1.5中引入,目的就是为了来处理**Runnable不支持的用例**。**Runnable 接口不会返回结果或抛出检查异常,但是Callable 接口可以**。所以,如果任务不需要返回结果或抛出异常推荐使用 Runnable 接口,这样代码看起来会更加简洁。 - -## Runnable源码 -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} -``` - -## Callable源码 -```java -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} -``` - -## 线程池的submit - -![看图说话](https://imgkr.cn-bj.ufileos.com/16a8713e-b2d1-408d-82e2-ac411a51b6e0.png) - -## 再瞧Thread - - -![](https://imgkr.cn-bj.ufileos.com/ff6bdf55-1616-4887-b320-1b21576f8211.png) - -有个疑问,没有FutureTask的参数哇? - -再看FutureTask: - -![](https://imgkr.cn-bj.ufileos.com/6f63d5a6-50a5-4412-b301-1bab8ce91b4d.png) - -它的参数有Callable和Runnable。 - -`public class FutureTask implements RunnableFuture` -源码实现了RunnableFuture,那我们跟进再看: - -```java -public interface RunnableFuture extends Runnable, Future { - /** - * Sets this Future to the result of its computation - * unless it has been cancelled. - */ - void run(); -} -``` -RunnableFuture继承了Runnable和Future。意味着FutureTask既有Runnable和Future的特性,Future的方法: - -![](https://imgkr.cn-bj.ufileos.com/5c856df9-feb9-43c0-9329-97db80239de0.png) - -可看出,Future的特性的get方法了。 diff --git "a/Interview/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index 6ca02b79..00000000 --- "a/Interview/codes/thread/ThreadLocal\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,38 +0,0 @@ -# ThreadLocal -**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。****如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。** - -## 原理 -```java -public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; - -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... -} -``` -从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。 - -`ThreadLocal`类的`set()`方法 - -```java -public void set(T value) { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) - map.set(this, value); - else - createMap(t, value); -} -ThreadLocalMap getMap(Thread t) { - return t.threadLocals; -} -``` -**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。** - -**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 - -## 内存泄露 -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法。 \ No newline at end of file diff --git "a/Interview/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index e6d70f2c..00000000 --- "a/Interview/codes/thread/synchronized\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,97 +0,0 @@ -# synchronized - -## 修饰范围 -先看例子: -```java -public class Test { - private final Object lock = new Object(); - - private static int money = 0; - - // 非静态方法 - public synchronized void noStaticMethod() { - money++; - } - // 静态方法 - public static synchronized void staticMethod() { - money++; - } - public void codeBlock() { - // 代码块 - synchronized(lock) { - money++; - } - } -} -``` -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁** - -## synchronized和ReentrantLock对比 -1. **两者都是可重入锁**:两者都是可重入锁。“可重入锁”概念是:**自己可以再次获取自己的内部锁**。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 -2. **Synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API**:synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 -3. **ReenTrantLock 比 Synchronized 增加了一些高级功能** - 1. **等待可中断**:过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 - 2. **可实现公平锁** - 3. **可实现选择性通知(锁可以绑定多个条件)**:线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” - 4. **性能已不是选择标准**:在jdk1.6之前synchronized 关键字吞吐量随线程数的增加,下降得非常严重。1.6之后,**synchronized 和 ReenTrantLock 的性能基本是持平了。** - -## 底层原理 -> **synchronized 关键字底层原理属于 JVM 层面。** - -1. **synchronized 同步语句块的情况** - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -1. **synchronized 修饰方法的的情况** - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - -## 1.6版本的优化 -> 在 Java 早期版本中,`synchronized` 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 **Mutex Lock** 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要**操作系统**帮忙完成,而操作系统实现线程之间的切换时需要从**用户态转换到内核态**,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如**自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等**技术来减少锁操作的开销。 - -锁主要存在四中状态,依次是:**无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态**,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -1. **偏向锁** - **引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 - 偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步! - 但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。**升级过程:** - 1. 访问Mark Word中**偏向锁的标识是否设置成1**,**锁标识位是否为01**,确认偏向状态 - 2. 如果为可偏向状态,则判断**当前线程ID是否为偏向线程** - 3. 如果偏向线程未当前线程,则通过**cas操作竞争锁**,如果竞争成功则操作Mark Word中线程ID设置为当前线程ID - 4. 如果cas偏向锁获取失败,则挂起当前偏向锁线程,偏向锁升级为**轻量级锁** - -3. **轻量级锁** - 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** - **轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!升级过程** : - 1. 线程由偏向锁升级为轻量级锁时,会先把**锁的对象头MarkWord复制一份到线程的栈帧中,建立一个名为锁记录空间(Lock Record),用于存储当前Mark Word的拷贝**。 - 2. 虚拟机使用cas操作尝试将**对象的Mark Word指向Lock Record的指针,并将Lock record里的owner指针指对象的Mark Word**。 - 3. 如果cas操作成功,则该线程拥有了对象的轻量级锁。第二个线程cas自旋锁等待锁线程释放锁。 - 4. 如果多个线程竞争锁,轻量级锁要膨胀为**重量级锁**,**Mark Word中存储的就是指向重量级锁(互斥量)的指针**。其他等待线程进入阻塞状态。 - -5. **自旋锁和自适应自旋** - 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 - 互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。 - - **一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 - - **在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 - -6. **锁消除** - - 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 - -7. **锁粗化** - - 原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 - -8. **总结升级过程**: - - 1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁 - 2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1 - 3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。 - 4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁 - 5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 - 6. 如果自旋成功则依然处于轻量级状态。 - 7. 如果自旋失败,则升级为重量级锁。 diff --git "a/Interview/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index fbba865f..00000000 --- "a/Interview/codes/thread/volatile\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,164 +0,0 @@ -# volatile -> 那肯定介绍的它的两大特性:内存可见性和可序性。当然,还会介绍它的底层原理 - -## 内存可见性 -```java -public class Test { - private volatile boolean isStart = true; - void m() { - System.out.println(Thread.currentThread().getName() + " start..."); - while (isStart) {} - System.out.println(Thread.currentThread().getName() + " end..."); - } - - public static void main(String[] args) { - Test t1 = new Test(); - new Thread(t1::m, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - t1.isStart = false; - } -} -``` - -分析:一开始isReady为true,m方法中的while会一直循环,而主线程开启开线程之后会延迟1s将isReady赋值为false,若不加volatile修饰,则程序一直在运行,若加了volatile修饰,则程序最后会输出t1 m end... - -## 可序性 -> 有序性是指程序代码的执行是按照代码的实现顺序来按序执行的;volatile的有序性特性则是指禁止JVM指令重排优化。 -```java -public class Test { - private volatile static Test instance = null; - private Test(){} - - private static Test getInstance() { - if (instance != null) { - synchronized (Test.class) { - if (instance != null) { - instance = new Test(); - } - } - } - return instance; - } -} -``` -上面的代码是一个很常见的单例模式实现方式,但是上述代码在多线程环境下是有问题的。为什么呢,问题出在instance对象的初始化上,因为`instance = new Singleton();`这个初始化操作并不是原子的,在JVM上会对应下面的几条指令: -``` -memory =allocate(); //1. 分配对象的内存空间 -ctorInstance(memory); //2. 初始化对象 -instance =memory; //3. 设置instance指向刚分配的内存地址 -``` -上面三个指令中,步骤2依赖步骤1,但是步骤3不依赖步骤2,所以JVM可能针对他们进行指令重拍序优化,重排后的指令如下: -``` -memory =allocate(); //1. 分配对象的内存空间 -instance =memory; //3. 设置instance指向刚分配的内存地址 -ctorInstance(memory); //2. 初始化对象 -``` -这样优化之后,内存的初始化被放到了instance分配内存地址的后面,这样的话当线程1执行步骤3这段赋值指令后,刚好有另外一个线程2进入getInstance方法判断instance不为null,这个时候线程2拿到的instance对应的内存其实还未初始化,这个时候拿去使用就会导致出错。 - -所以我们在用这种方式实现单例模式时,会使用volatile关键字修饰instance变量,这是因为volatile关键字除了可以保证变量可见性之外,还具有防止指令重排序的作用。当用volatile修饰instance之后,JVM执行时就不会对上面提到的初始化指令进行重排序优化,这样也就不会出现多线程安全问题了。 - -## 不能保证原子性 -> 20个线程,每个线程count累加10000次,最后count理论上是200000 -```java -public class Test { - volatile int count = 0; - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - for (int i = 0; i < 10000; i++) { - count++; - } - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch latch = new CountDownLatch(20); - Test t1 = new Test(latch); - for (int i = 0; i < 20; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - latch.await(); - System.out.println(t1.count); - } -// 85121 -``` -分析图:[https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c](https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c) - -## 整一波内存屏障 -> **Java的Volatile的特征是任何读都能读到最新值,本质上是JVM通过内存屏障来实现的;为了实现volatile内存语义,JMM会分别限制重排序类型。下面是JMM针对编译器制定的volatile重排序规则表:** - - | 是否能重排序 | 第二个操作 | | | -| :----------: | :--------: | :--------: | :--------: | - | 第一个操作 | 普通读/写 | volatile读 | volatile写 | - | 普通读/写 | | | no | - | volatile读 | no | no | no | - | volatile写 | | no | no | - -**从上表我们可以看出**: -- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 -- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 -- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。 -**为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:** -- 在每个volatile写操作的前面插入一个StoreStore屏障。 -- 在每个volatile写操作的后面插入一个StoreLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadStore屏障。 - -**volatile写插入内存指令图:** -![volatile写插入](https://image-static.segmentfault.com/416/041/416041851-5ac871370fec9_articlex) - -**上图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。** - -> **这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面,是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在这里采取了保守策略:在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。** - -**volatile读插入内存指令图:** -![volatile读](https://image-static.segmentfault.com/288/764/2887649856-5ac871c442f52_articlex) - -**上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。** - -**上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明:** - -```java -class VolatileBarrierExample { - int a; - volatile int v1 = 1; - volatile int v2 = 2; - - void readAndWrite() { - int i = v1; //第一个volatile读 - int j = v2; // 第二个volatile读 - a = i + j; //普通写 - v1 = i + 1; // 第一个volatile写 - v2 = j * 2; //第二个 volatile写 - } -} -``` -针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化: -![readAndWrite](https://image-static.segmentfault.com/178/456/1784565222-5ac871e5e6dec_articlex) - -**注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器常常会在这里插入一个StoreLoad屏障。** - -## volatile的汇编 -```c -0x000000011214bb49: mov %rdi,%rax -0x000000011214bb4c: dec %eax -0x000000011214bb4e: mov %eax,0x10(%rsi) -0x000000011214bb51: lock addl $0x0,(%rsp) ;*putfield v1 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35) - -0x000000011214bb56: imul %edi,%ebx -0x000000011214bb59: mov %ebx,0x14(%rsi) -0x000000011214bb5c: lock addl $0x0,(%rsp) ;*putfield v2 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36) -``` -**可见其本质是通过一个lock指令来实现的。那么lock是什么意思呢?** - -**它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。** - -- 锁住内存 -- 任何读必须在写完成之后再执行 -- 使其它线程这个值的栈缓存失效 diff --git "a/Interview/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" "b/Interview/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" deleted file mode 100644 index 41239eb7..00000000 --- "a/Interview/codes/thread/\344\270\273\347\272\277\347\250\213\347\255\211\345\276\205\345\244\232\344\270\252\345\255\220\347\272\277\347\250\213\347\232\204\344\276\213\345\255\220.md" +++ /dev/null @@ -1,73 +0,0 @@ -# 主线程等待多个子线程的例子 - -## sleep -> 这个不常用,但是简单一些 -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("main thread"); - } -} -``` - -## join - -```java -public class Test { - void m() { - System.out.println(Thread.currentThread().getName()); - } - - public static void main(String[] args) { - Test t1 = new Test(); - ArrayList threads = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - threads.add(new Thread(t1::m, "Thread " + i)); - } - threads.forEach(o -> o.start()); - threads.forEach(o -> { - try { - o.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - System.out.println("main thread"); - } -} -``` - -## CountDownLatch -```java -public class Test { - private CountDownLatch latch; - - public Test(CountDownLatch latch) { - this.latch = latch; - } - - void m() { - System.out.println(Thread.currentThread().getName()); - latch.countDown(); - } - - public static void main(String[] args) throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(5); - Test t1 = new Test(countDownLatch); - for (int i = 0; i < 5; i++) { - new Thread(t1::m, "Thread " + i).start(); - } - countDownLatch.await(); - System.out.println("main thread"); - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" "b/Interview/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" deleted file mode 100644 index eed0016c..00000000 --- "a/Interview/codes/thread/\344\270\276\344\270\252\345\244\232\347\272\277\347\250\213\347\232\204\345\260\217\344\276\213\345\255\220.md" +++ /dev/null @@ -1,94 +0,0 @@ -# 举个多线程的小例子 -> 开启10个线程,每个线程对同一个变量进行1000次加1操作。 - -## V1 -```java -public class Test { - int count; - void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -// 运行结果 -// 9378 -// 想知道原因,得需要知道Java内存模型... -``` -## V2(volatile) -```java -public class Test { - volatile int count; - void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` -count++是非原子性操作,即使使用volatile保证内存可见性,但是无法保证原子性,因此,还是凉凉 - -## V3(synchronized) -```java -public class Test { - int count; - synchronized void m() { - for(int i = 0; i < 1000; i++) { - count++; - } - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` -这里可以得到线程安全同步,但是效率似乎有点慢,毕竟这个操作是自增。 - -## V4(CAS) -```java -public class Test { - AtomicInteger count = new AtomicInteger(0); - void m() { - count.incrementAndGet(); - } - - public static void main(String[] args) { - Test t1 = new Test(); - for (int i = 0; i < 10; i++){ - new Thread(t1::m, "Thread " + i).start(); - } - // 等待完成,这里有多种方式... - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(t1.count); - } -} -``` - -像Volatile、synchronized、CAS稍后再说... \ No newline at end of file diff --git "a/Interview/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" "b/Interview/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" deleted file mode 100644 index f63c7eed..00000000 --- "a/Interview/codes/thread/\346\255\273\351\224\201\344\276\213\345\255\220.md" +++ /dev/null @@ -1,38 +0,0 @@ -# 死锁例子 - -## 死锁的四个条件 -- 互斥条件:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) -- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) -- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) -- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗) - -## 例子 -```java -public class Test { - private static Object res1 = new Object(); - private static Object res2 = new Object(); - - public static void main(String[] args) { - new Thread(() -> { - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - // 延迟一下, 确保B拿到了res2 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - } - } - }, "ThreadA").start(); - - new Thread(() -> { - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " res2"); - // 延迟一下,确保A拿到了res1 - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " res1"); - } - } - }, "ThreadB").start(); - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" "b/Interview/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" deleted file mode 100644 index d68976cc..00000000 --- "a/Interview/codes/thread/\347\224\237\344\272\247\350\200\205\345\222\214\346\266\210\350\264\271\350\200\205\347\232\204\344\270\211\347\247\215\344\276\213\345\255\220.md" +++ /dev/null @@ -1,192 +0,0 @@ -# 生产者和消费者 -> 在这里有三种方式实现:synchronized、ReentrantLock和BlockingQueue - -## synchronized -```java -public class ProdConsumerSynchronized { - - private final LinkedList lists = new LinkedList<>(); - - public synchronized void put(String s) { - while (lists.size() != 0) { // 用while怕有存在虚拟唤醒线程 - // 满了, 不生产了 - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - this.notifyAll(); // 这里可是通知所有被挂起的线程,包括其他的生产者线程 - } - - public synchronized void get() { - while (lists.size() == 0) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - this.notifyAll(); // 通知所有被wait挂起的线程 用notify可能就死锁了。 - } - - public static void main(String[] args) { - ProdConsumerSynchronized prodConsumerSynchronized = new ProdConsumerSynchronized(); - - // 启动消费者线程 - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerSynchronized::get, "ConsA" + i).start(); - } - - // 启动生产者线程 - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerSynchronized.put("" + tempI); - }, "ProdA" + i).start(); - } - } -} -``` - -## ReentrantLock -```java -public class ProdConsumerReentrantLock { - - private LinkedList lists = new LinkedList<>(); - - private Lock lock = new ReentrantLock(); - - private Condition prod = lock.newCondition(); - - private Condition cons = lock.newCondition(); - - public void put(String s) { - lock.lock(); - try { - // 1. 判断 - while (lists.size() != 0) { - // 等待不能生产 - prod.await(); - } - // 2.干活 - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - // 3. 通知 - cons.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public void get() { - lock.lock(); - try { - // 1. 判断 - while (lists.size() == 0) { - // 等待不能消费 - cons.await(); - } - // 2.干活 - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - // 3. 通知 - prod.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public static void main(String[] args) { - ProdConsumerReentrantLock prodConsumerReentrantLock = new ProdConsumerReentrantLock(); - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerReentrantLock.put(tempI + ""); - }, "ProdA" + i).start(); - } - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerReentrantLock::get, "ConsA" + i).start(); - } - } -} -``` -## BlockingQueue -```java -public class ProdConsumerBlockingQueue { - - private volatile boolean flag = true; - - private AtomicInteger atomicInteger = new AtomicInteger(); - - BlockingQueue blockingQueue = null; - - public ProdConsumerBlockingQueue(BlockingQueue blockingQueue) { - this.blockingQueue = blockingQueue; - } - - public void myProd() throws Exception { - String data = null; - boolean retValue; - while (flag) { - data = atomicInteger.incrementAndGet() + ""; - retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS); - if (retValue) { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 成功"); - } else { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 失败"); - } - TimeUnit.SECONDS.sleep(1); - } - System.out.println(Thread.currentThread().getName() + " 大老板叫停了,flag=false,生产结束"); - } - - public void myConsumer() throws Exception { - String result = null; - while (flag) { - result = blockingQueue.poll(2, TimeUnit.SECONDS); - if (null == result || result.equalsIgnoreCase("")) { - flag = false; - System.out.println(Thread.currentThread().getName() + " 超过2s没有取到蛋糕,消费退出"); - return; - } - System.out.println(Thread.currentThread().getName() + " 消费队列" + result + "成功"); - } - } - - public void stop() { - flag = false; - } - - public static void main(String[] args) { - ProdConsumerBlockingQueue prodConsumerBlockingQueue = new ProdConsumerBlockingQueue(new ArrayBlockingQueue<>(10)); - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 生产线程启动"); - try { - prodConsumerBlockingQueue.myProd(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Prod").start(); - - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 消费线程启动"); - try { - prodConsumerBlockingQueue.myConsumer(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Consumer").start(); - - try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("5s后main叫停,线程结束"); - prodConsumerBlockingQueue.stop(); - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index b5afec2a..00000000 --- "a/Interview/codes/thread/\347\272\277\347\250\213\346\261\240\347\232\204\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,136 +0,0 @@ -# 线程池 -线程池的好处 -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -## FixedThreadPool -`FixedThreadPool` 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现: -```java -/** - * 创建一个可重用固定数量线程的线程池 - */ -public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { - return new ThreadPoolExecutor(nThreads, nThreads, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory); -} -``` -**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。** -1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务; -2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`; -3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行; - -不推荐使用 - -**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :** - -1. 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize; -2. 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。 -3. 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数; -4. 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。 - -## SingleThreadExecutor -```java -/** - *返回只有一个线程的线程池 - */ -public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { - return new FinalizableDelegatedExecutorService - (new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory)); -} -``` -和上面一个差不多,只不过core和max都被设置为1 - -## CachedThreadPool -```java -/** - * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。 - */ -public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue(), - threadFactory); -} -``` -`CachedThreadPool` 的` corePoolSize` 被设置为空(0),`maximumPoolSize `被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。 - -1. 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2; -2. 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成; - -## ThreadPoolExecutor(重点) -```java -/** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ -public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) -``` - -**`ThreadPoolExecutor` 3 个最重要的参数:** - -- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。 -- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: - -- **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁; -- **`unit`** : `keepAliveTime` 参数的时间单位。 -- **`threadFactory`** :executor 创建新线程的时候会用到。 -- **`handler`** :饱和策略。关于饱和策略下面单独介绍一下. - -![线程池各个参数的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/线程池各个参数的关系.jpg) - -- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。 -- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。 -- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。 - -> Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了。) - -**Executors 返回线程池对象的弊端如下**: - -- **`FixedThreadPool` 和 `SingleThreadExecutor`** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。 -- **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。 - -```java -public class ThreadPoolExecutorDemo { - public static void main(String[] args) { - ExecutorService threadpools = new ThreadPoolExecutor( - 3, - 5, - 1l, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(3), - Executors.defaultThreadFactory(), - new ThreadPoolExecutor.AbortPolicy()); -//new ThreadPoolExecutor.AbortPolicy(); -//new ThreadPoolExecutor.CallerRunsPolicy(); -//new ThreadPoolExecutor.DiscardOldestPolicy(); -//new ThreadPoolExecutor.DiscardPolicy(); - try { - for (int i = 0; i < 8; i++) { - threadpools.execute(() -> { - System.out.println(Thread.currentThread().getName() + "\t办理业务"); - }); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - threadpools.shutdown(); - } - } -} -``` \ No newline at end of file diff --git "a/Interview/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" "b/Interview/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" deleted file mode 100644 index c6d28b1e..00000000 --- "a/Interview/codes/thread/\347\272\277\347\250\213\347\224\237\345\221\275\345\221\250\346\234\237\344\270\200\344\272\233\351\227\256\351\242\230.md" +++ /dev/null @@ -1,90 +0,0 @@ -# 线程的生命周期 - -先放一张图: -![线程周期图](https://www.pdai.tech/_images/pics/ace830df-9919-48ca-91b5-60b193f593d2.png) - -线程创建之后它将处于**New**(新建)状态,调用 **start()** 方法后开始运行,线程这时候处于 **READY**(可运行) 状态。可运行状态的线程获得了 **CPU 时间片**(timeslice)后就处于 **RUNNING**(运行) 状态。 - -- 当线程执行 **wait()**方法之后,线程进入 **WAITING**(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态 -- 而 **TIME_WAITING**(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 **sleep**(long millis)方法或 **wait**(long millis)方法可以将 Java 线程置于 **TIMED_WAITING** 状态。当超时时间到达后 Java 线程将会返回到 **RUNNABLE** 状态。 -- 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞)状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。 - -## 上下文切换 - -> 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,**CPU 采取的策略是为每个线程分配时间片并轮转的形式**。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。 -实际上就是任务从保存到再加载的过程就是一次上下文切换。 上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 - -## 直接调用Thread的run方法不行吗? -### 区别 -> Java中启动线程有两种方法,继承Thread类和实现Runnable接口,由于Java无法实现多重继承,所以一般通过实现Runnable接口来创建线程。但是无论哪种方法都可以通过start()和run()方法来启动线程,下面就来介绍一下他们的区别。 - -#### start -通过该方法启动线程的同时**也创建了一个线程,真正实现了多线程**。**无需等待run()方法中的代码执行完毕,就可以接着执行下面的代码**。此时start()的这个线程处于**就绪状态**,当得到CPU的时间片后就会执行其中的run()方法。这个run()方法包含了要执行的这个线程的内容,run()方法运行结束,此线程也就终止了。 - -#### run -通过run方法启动线程其实就是调用一个类中的方法,**当作普通的方法的方式调用**。并没有创建一个线程,程序中依旧只有一个主线程,必须等到run()方法里面的代码执行完毕,才会继续执行下面的代码,这样就没有达到写线程的目的。 - -```java -public class Test { - public static void main(String[] args) { - Thread t = new Thread(){ - public void run() { - cat(); - } - }; - t.run(); - System.out.println("dream"); - } - - static void cat() { - System.out.println("cat"); - } -} -// 结果: -// cat -// dream -``` -如果将`t.run()`改成`t.start()`; -```java -dream -cat -``` - -### 探究一下源码 -看一下start方法源码: -```java -public synchronized void start() { - if (threadStatus != 0) - throw new IllegalThreadStateException(); - group.add(this); - - boolean started = false; - try { - start0(); - started = true; - } finally { - try { - if (!started) { - group.threadStartFailed(this); - } - } catch (Throwable ignore) { - /* do nothing. If start0 threw a Throwable then - it will be passed up the call stack */ - } - } -} - -private native void start0(); -``` - 可以看到,当一个线程启动的时候,它的状态(threadStatus)被设置为0,如果不为0,则抛出`IllegalThreadStateException`异常。正常的话,将该**线程加入线程组**,最后尝试调用start0方法,**而start0方法是私有的native方法**(Native Method是一个java调用非java代码的接口)。 - -我们再看一下run方法源码: -```java -public void run() { - if (target != null) { - target.run(); - } -} -``` -target的定义:`private Runnable target;` -所以其实就是一个Runnable接口,正如上面代码中new Thread的部分,其实我们就是在实现它的run()方法。所以如果**直接调用run,就和一个普通的方法没什么区别,是不会创建新的线程的,因为压根就没执行start0方法**。 diff --git "a/Interview/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" "b/Interview/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" deleted file mode 100644 index 08795ae1..00000000 --- "a/Interview/codes/thread/\347\272\277\347\250\213\347\232\204\345\220\257\345\212\250\346\226\271\345\274\217.md" +++ /dev/null @@ -1,53 +0,0 @@ -# 线程的启动方式 - -## Thread -```java -public class Test extents Thread { - public void run() { - // 重写Thread的run方法 - System.out.println("dream"); - } - - public static void main(String[] args) { - new Test().start(); - } -} -``` - -## Runnable -```java -public class Test { - public static void main(String[] args) { - new Thread(() -> { - System.out.println("dream"); - }).start(); - } -} -``` - -## Callable -```java -public class Test { - public static void main(String[] args) { - // FutureTask 构造方法包装了Callable和Runnable。 - FutureTask task = new FutureTask<>(() -> { - System.out.println("dream"); - return 0; - }); - new Thread(task).start(); - } -} -``` - -## 线程池 -```java -public class Test { - public static void main(String[] args) { - ExecutorService threadPool = Executors.newFixedThreadPool(1); - threadPool.submit(() -> { - System.out.println("dream"); - }); - threadPool.shutdown(); - } -} -``` diff --git a/Interview/crazy/.DS_Store b/Interview/crazy/.DS_Store deleted file mode 100644 index 5008ddfc..00000000 Binary files a/Interview/crazy/.DS_Store and /dev/null differ diff --git a/Interview/crazy/README.md b/Interview/crazy/README.md deleted file mode 100644 index 3d9d8964..00000000 --- a/Interview/crazy/README.md +++ /dev/null @@ -1,11 +0,0 @@ -- **个人吐血系列-总结Java基础**: [本地阅读](Java基础.md)->[博客阅读](http://dreamcat.ink/2020/03/27/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-chu/)->[掘金阅读](https://juejin.im/post/5e7e0615f265da795568754b) -- **个人吐血系列-总结Java集合**: [本地阅读](Java集合.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-he/)->[掘金阅读](https://juejin.im/post/5e801e29e51d45470b4fce1c) -- **个人吐血系列-总结Java多线程**: [本地阅读](Java多线程)->[博客阅读](http://dreamcat.ink/2020/03/25/ge-ren-tu-xie-xi-lie-zong-jie-java-duo-xian-cheng/)->[掘金阅读-1](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) [掘金阅读-2](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) -- **个人吐血系列-总结JVM**: [本地阅读](JVM.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-jvm/)->[掘金阅读](https://juejin.im/post/5e8344486fb9a03c786ef885) -- **个人吐血系列-总结Spring**: [本地阅读](Spring.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-spring/)->[掘金阅读](https://juejin.im/post/5e846a4a6fb9a03c42378bc1) -- **个人吐血系列-总结Mybatis**: [本地阅读](Mybatis.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-mybatis/)->[掘金阅读](https://juejin.im/post/5e889b196fb9a03c875c8f50) -- **个人吐血系列-总结MySQL**: [本地阅读](MySQL.md)->[博客阅读](http://dreamcat.ink/2020/03/30/ge-ren-tu-xie-xi-lie-zong-jie-mysql/)->[掘金阅读](https://juejin.im/post/5e94116551882573b86f970f) -- **个人吐血系列-总结Redis**: [本地阅读](Redis.md)->[博客阅读](http://dreamcat.ink/2020/03/31/ge-ren-tu-xie-xi-lie-zong-jie-redis/)->[掘金阅读](https://juejin.im/post/5e9d6a9ff265da47e34c0e8a) -- **个人吐血系列-总结计算机网络**: [本地阅读](计算机网络.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)->[掘金阅读](https://juejin.im/post/5ea383c251882573716ab496) -- **个人吐血系列-Dubbo**: [本地阅读](Dubbo.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)->[掘金阅读](https://juejin.im/post/5eb11127f265da7bb46bce26) -- **个人吐血系列-RocketMQ**: [本地阅读](RocketMQ)->[博客阅读](http://dreamcat.ink/2020/04/01/ge-ren-tu-xie-xi-lie-zong-jie-rocketmq/)-> [掘金阅读](https://juejin.im/post/5ecf1f716fb9a047f338b972) \ No newline at end of file diff --git "a/Interview/linux/linux-\345\237\272\347\241\200.md" "b/Interview/linux/linux-\345\237\272\347\241\200.md" deleted file mode 100644 index fb815ec4..00000000 --- "a/Interview/linux/linux-\345\237\272\347\241\200.md" +++ /dev/null @@ -1,695 +0,0 @@ -## 引言 - -**linux-基础** - -## 常用操作以及概念 - -### 快捷键 - -- Tab: 命令和文件名补全; -- Ctrl+C: 中断正在运行的程序; -- Ctrl+D: 结束键盘输入(End Of File,EOF) - -### 求助 - - - -#### --help - -指令的基本用法与选项介绍。 - -#### man - -man 是 manual 的缩写,将指令的具体信息显示出来。 - -#### info - -info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。 - -#### doc - -/usr/share/doc 存放着软件的一整套说明文件。 - -### 关机 - -#### who - -在关机前需要先使用 who 命令查看有没有其它用户在线。 - -#### sync - -为了加快对磁盘文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。 - -#### shutdown - -```bash -## shutdown [-krhc] 时间 [信息] --k : 不会关机,只是发送警告信息,通知所有在线的用户 --r : 将系统的服务停掉后就重新启动 --h : 将系统的服务停掉后就立即关机 --c : 取消已经在进行的 shutdown 指令内容 -``` - -### PATH - -可以在环境变量 PATH 中声明可执行文件的路径,路径之间用 : 分隔。 - -`/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin` - -### sudo - -sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令. - -### 包管理工具 - -RPM 和 DPKG 为最常见的两类软件包管理工具: - -- RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。 -- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。 - -YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能。 - -### 发行版 - -Linux 发行版是 Linux 内核及各种应用软件的集成版本。 - -| 基于的包管理工具 | 商业发行版 | 社区发行版 | -| :--------------: | :--------: | :-------------: | -| RPM | Red Hat | Fedora / CentOS | -| DPKG | Ubuntu | Debian | - -### VIM - -- 一般指令模式(Command mode): VIM 的默认模式,可以用于移动游标查看内容; -- 编辑模式(Insert mode): 按下 "i" 等按键之后进入,可以对文本进行编辑; -- 指令列模式(Bottom-line mode): 按下 ":" 按键之后进入,用于保存退出等操作。 - -在指令列模式下,有以下命令用于离开或者保存文件。 - -| 命令 | 作用 | -| :--: | :----------------------------------------------------------: | -| :w | 写入磁盘 | -| :w! | 当文件为只读时,强制写入磁盘。到底能不能写入,与用户对该文件的权限有关 | -| :q | 离开 | -| :q! | 强制离开不保存 | -| :wq | 写入磁盘后离开 | -| :wq! | 强制写入磁盘后离开 | - -## 磁盘 - -### 磁盘接口 - -#### IDE - -IDE(ATA)全称 Advanced Technology Attachment,接口速度最大为 133MB/s,因为并口线的抗干扰性太差,且排线占用空间较大,不利电脑内部散热,已逐渐被 SATA 所取代。 - -#### SATA - -SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能。SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。 - -#### SCSI - -SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。 - -#### SAS - -SAS(Serial Attached SCSI)是新一代的 SCSI 技术,和 SATA 硬盘相同,都是采取序列式技术以获得更高的传输速度,可达到 6Gb/s。此外也透过缩小连接线改善系统内部空间等。 - -## 分区 - -### 分区表 - -磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。 - -#### MBR - -MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。 - -分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 - -Linux 也把分区当成文件,分区文件的命名方式为: 磁盘文件名 + 编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 - -#### GPT - -不同的磁盘有不同的扇区大小,例如 512 bytes 和最新磁盘的 4 k。GPT 为了兼容所有磁盘,在定义扇区上使用逻辑区块地址(Logical Block Address, LBA),LBA 默认大小为 512 bytes。 - -GPT 第 1 个区块记录了主要开机记录(MBR),紧接着是 33 个区块记录分区信息,并把最后的 33 个区块用于对分区信息进行备份。这 33 个区块第一个为 GPT 表头纪录,这个部份纪录了分区表本身的位置与大小和备份分区的位置,同时放置了分区表的校验码 (CRC32),操作系统可以根据这个校验码来判断 GPT 是否正确。若有错误,可以使用备份分区进行恢复。 - -GPT 没有扩展分区概念,都是主分区,每个 LAB 可以分 4 个分区,因此总共可以分 4 * 32 = 128 个分区。 - -MBR 不支持 2.2 TB 以上的硬盘,GPT 则最多支持到 233 TB = 8 ZB。 - -### 开机检测程序 - -#### BIOS - -BIOS(Basic Input/Output System,基本输入输出系统),它是一个固件(嵌入在硬件中的软件),BIOS 程序存放在断电后内容不会丢失的只读内存中。 - -BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。 - -主要开机记录(MBR)中的开机管理程序提供以下功能: 选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动开机管理程序时,就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 - -下图中,第一扇区的主要开机记录(MBR)中的开机管理程序提供了两个选单: M1、M2,M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。 - -安装多重引导,最好先安装 Windows 再安装 Linux。因为安装 Windows 时会覆盖掉主要开机记录(MBR),而 Linux 可以选择将开机管理程序安装在主要开机记录(MBR)或者其它分区的启动扇区,并且可以设置开机管理程序的选单。 - -#### UEFI - -BIOS 不可以读取 GPT 分区表,而 UEFI 可以。 - -## 文件系统 - -### 分区与文件系统 - -对分区进行格式化是为了在分区上建立文件系统。一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统。 - -### 组成 - -最主要的几个组成部分如下: - -- inode: 一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block 编号; -- block: 记录文件的内容,文件太大时,会占用多个 block。 -- superblock: 记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; -- block bitmap: 记录 block 是否被使用的位域。 - -### 文件读取 - -对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。 - -### 磁盘碎片 - -指一个文件内容所在的 block 过于分散。 - -### Block - -在 Ext2 文件系统中所支持的 block 大小有 1K,2K 及 4K 三种,不同的大小限制了单个文件和文件系统的最大大小。 - -一个 block 只能被一个文件所使用,未使用的部分直接浪费了。因此如果需要存储大量的小文件,那么最好选用比较小的 block。 - -### inode - -inode 具体包含以下信息: - -- 权限 (read/write/excute); -- 拥有者与群组 (owner/group); -- 容量; -- 建立或状态改变的时间 (ctime); -- 最近一次的读取时间 (atime); -- 最近修改的时间 (mtime); -- 定义文件特性的旗标 (flag),如 SetUID...; -- 该文件真正内容的指向 (pointer)。 - -inode 具有以下特点: - -- 每个 inode 大小均固定为 128 bytes (新的 ext4 与 xfs 可设定到 256 bytes); -- 每个文件都仅会占用一个 inode。 - -inode 中记录了文件内容所在的 block 编号,但是每个 block 非常小,一个大文件随便都需要几十万的 block。而一个 inode 大小有限,无法直接引用这么多 block 编号。因此引入了间接、双间接、三间接引用。间接引用是指,让 inode 记录的引用 block 块记录引用信息。 - -### 目录 - -建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名。 - -可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。 - -### 日志 - -如果突然断电,那么文件系统会发生错误,例如断电前只修改了 block bitmap,而还没有将数据真正写入 block 中。 - -ext3/ext4 文件系统引入了日志功能,可以利用日志来修复文件系统。 - -### 挂载 - -挂载利用目录作为文件系统的进入点,也就是说,进入目录之后就可以读取文件系统的数据。 - -### 目录配置 - -为了使不同 Linux 发行版本的目录结构保持一致性,Filesystem Hierarchy Standard (FHS) 规定了 Linux 的目录结构。最基础的三个目录如下: - -- / (root, 根目录) -- /usr (unix software resource): 所有系统默认软件都会安装到这个目录; -- /var (variable): 存放系统或程序运行过程中的数据文件。 - -## 文件 - -### 文件属性 - -用户分为三种: 文件拥有者、群组以及其它人,对不同的用户有不同的文件权限。 - -使用 ls 查看一个文件时,会显示一个文件的信息,例如 `drwxr-xr-x. 3 root root 17 May 6 00:14 .config`,对这个信息的解释如下: - -- drwxr-xr-x: 文件类型以及权限,第 1 位为文件类型字段,后 9 位为文件权限字段 -- 3: 链接数 -- root: 文件拥有者 -- root: 所属群组 -- 17: 文件大小 -- May 6 00:14: 文件最后被修改的时间 -- .config: 文件名 - -常见的文件类型及其含义有: - -- d: 目录 -- -: 文件 -- l: 链接文件 - -9 位的文件权限字段中,每 3 个为一组,共 3 组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3 位分别为 r、w、x 权限,表示可读、可写、可执行。 - -文件时间有以下三种: - -- modification time (mtime): 文件的内容更新就会更新; -- status time (ctime): 文件的状态(权限、属性)更新就会更新; -- access time (atime): 读取文件时就会更新。 - -### 文件与目录的基本操作 - -#### ls - -列出文件或者目录的信息,目录的信息就是其中包含的文件。 - -```bash -## ls [-aAdfFhilnrRSt] file|dir --a : 列出全部的文件 --d : 仅列出目录本身 --l : 以长数据串行列出,包含文件的属性与权限等等数据 - -``` - -#### cd - -更新当前目录 - -`cd [相对路径或者绝对路径]` - -#### mkdir - -创建目录 - -```bash -## mkdir [-mp] 目录名称 --m : 配置目录权限 --p : 递归创建目录 -``` - -#### rmdir - -删除目录,目录必须为空 - -```bash -rmdir [-p] 目录名称 --p : 递归删除目录 -``` - -#### touch - -更新文件时间或者建立新文件 - -```bash -## touch [-acdmt] filename --a : 更新 atime --c : 更新 ctime,若该文件不存在则不建立新文件 --m : 更新 mtime --d : 后面可以接更新日期而不使用当前日期,也可以使用 --date="日期或时间" --t : 后面可以接更新时间而不使用当前时间,格式为[YYYYMMDDhhmm] - -``` - -#### cp - -复制文件 - -如果源文件有两个以上,则目的文件一定要是目录才行。 - -```bash -cp [-adfilprsu] source destination --a : 相当于 -dr --preserve=all 的意思,至于 dr 请参考下列说明 --d : 若来源文件为链接文件,则复制链接文件属性而非文件本身 --i : 若目标文件已经存在时,在覆盖前会先询问 --p : 连同文件的属性一起复制过去 --r : 递归持续复制 --u : destination 比 source 旧才更新 destination,或 destination 不存在的情况下才复制 ---preserve=all : 除了 -p 的权限相关参数外,还加入 SELinux 的属性, links, xattr 等也复制了 - -``` - -#### rm - -删除文件 - -```bash -## rm [-fir] 文件或目录 --r : 递归删除 -``` - -#### mv - -移动文件 - -```bash -## mv [-fiu] source destination -## mv [options] source1 source2 source3 .... directory --f : force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -``` - -### 修改权限 - -可以将一组权限用数字来表示,此时一组权限的 3 个位当做二进制数字的位,从左到右每个位的权值为 4、2、1,即每个权限对应的数字权值为 r : 4、w : 2、x : 1。 - -`## chmod [-R] xyz dirname/filename` - -示例: 将 .bashrc 文件的权限修改为 -rwxr-xr--。 - -`## chmod 754 .bashrc` - -也可以使用符号来设定权限。 - -```bash -## chmod [ugoa] [+-=] [rwx] dirname/filename -- u: 拥有者 -- g: 所属群组 -- o: 其他人 -- a: 所有人 -- +: 添加权限 -- -: 移除权限 -- =: 设定权限 -``` - -示例: 为 .bashrc 文件的所有用户添加写权限。 - -`## chmod a+w .bashrc` - -### 文件默认权限 - -- 文件默认权限: 文件默认没有可执行权限,因此为 666,也就是 -rw-rw-rw- 。 -- 目录默认权限: 目录必须要能够进入,也就是必须拥有可执行权限,因此为 777 ,也就是 drwxrwxrwx。 - -可以通过 umask 设置或者查看文件的默认权限,通常以掩码的形式来表示,例如 002 表示其它用户的权限去除了一个 2 的权限,也就是写权限,因此建立新文件时默认的权限为 -rw-rw-r--。 - -### 目录的权限 - -文件名不是存储在一个文件的内容中,而是存储在一个文件所在的目录中。因此,拥有文件的 w 权限并不能对文件名进行修改。 - -目录存储文件列表,一个目录的权限也就是对其文件列表的权限。因此,目录的 r 权限表示可以读取文件列表;w 权限表示可以修改文件列表,具体来说,就是添加删除文件,对文件名进行修改;x 权限可以让该目录成为工作目录,x 权限是 r 和 w 权限的基础,如果不能使一个目录成为工作目录,也就没办法读取文件列表以及对文件列表进行修改了。 - -### 链接 - -```bash -## ln [-sf] source_filename dist_filename --s : 默认是 hard link,加 -s 为 symbolic link --f : 如果目标文件存在时,先删除目标文件 -``` - -### 实体链接 - -在目录下创建一个条目,记录着文件名与 inode 编号,这个 inode 就是源文件的 inode。 - -删除任意一个条目,文件还是存在,只要引用数量不为 0。 - -有以下限制: 不能跨越文件系统、不能对目录进行链接。 - -```bash -## ln /etc/crontab . -## ll -i /etc/crontab crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -``` - -### 符号链接 - -符号链接文件保存着源文件所在的绝对路径,在读取时会定位到源文件上,可以理解为 Windows 的快捷方式。 - -当源文件被删除了,链接文件就打不开了。 - -可以为目录建立链接。 - -```bash -## ll -i /etc/crontab /root/crontab2 -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab -``` - -### 获取文件内容 - -#### cat - -取得文件内容。 - -```bash -## cat [-AbEnTv] filename --n : 打印出行号,连同空白行也会有行号,-b 不会 -``` - -#### tac - -是 cat 的反向操作,从最后一行开始打印。 - -#### more - -和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。 - -#### less - -和 more 类似,但是多了一个向前翻页的功能。 - -#### head - -取得文件前几行。 - -```bash -## head [-n number] filename --n : 后面接数字,代表显示几行的意思 -``` - -#### tail - -是head的反向操作,之水取得是后几行 - -#### od - -以字符或者十六进制的形式显示二进制文件。 - -### 指令与文件搜索 - -#### which - -指令搜索 - -```bash -## which [-a] command --a : 将所有指令列出,而不是只列第一个 -``` - -#### whereis - -文件搜索。速度比较快,因为它只搜索几个特定的目录。 - -```bash -## whereis [-bmsu] dirname/filename -``` - -#### locate - -文件搜索。可以用关键字或者正则表达式进行搜索。 - -locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内存中,并且每天更新一次,所以无法用 locate 搜索新建的文件。可以使用 updatedb 来立即更新数据库。 - -```bash -## locate [-ir] keyword --r: 正则表达式 -``` - -#### find - -文件搜索。可以使用文件的属性和权限进行搜索。 - -```bash -## find [basedir] [option] -example: find . -name "shadow*" -``` - -## 压缩与打包 - -### 压缩文件名 - -#### gzip - -gzip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。 - -经过 gzip 压缩过,源文件就不存在了。 - -有 9 个不同的压缩等级可以使用。 - -可以使用 zcat、zmore、zless 来读取压缩文件的内容。 - -```bash -$ gzip [-cdtv#] filename --c : 将压缩的数据输出到屏幕上 --d : 解压缩 --t : 检验压缩文件是否出错 --v : 显示压缩比等信息 --## : ## 为数字的意思,代表压缩等级,数字越大压缩比越高,默认为 6 -``` - -#### bzip2 - -提供比 gzip 更高的压缩比。 - -查看命令: bzcat、bzmore、bzless、bzgrep。 - -#### xz - -提供比 bzip2 更佳的压缩比。 - -可以看到,gzip、bzip2、xz 的压缩比不断优化。不过要注意的是,压缩比越高,压缩的时间也越长。 - -查看命令: xzcat、xzmore、xzless、xzgrep。 - -### 打包 - -压缩指令只能对一个文件进行压缩,而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。 - -```bash -$ tar [-z|-j|-J] [cv] [-f 新建的 tar 文件] filename... ==打包压缩 -$ tar [-z|-j|-J] [tv] [-f 已有的 tar 文件] ==查看 -$ tar [-z|-j|-J] [xv] [-f 已有的 tar 文件] [-C 目录] ==解压缩 --z : 使用 zip; --j : 使用 bzip2; --J : 使用 xz; --c : 新建打包文件; --t : 查看打包文件里面有哪些文件; --x : 解打包或解压缩的功能; --v : 在压缩/解压缩的过程中,显示正在处理的文件名; --f : filename: 要处理的文件; --C 目录 : 在特定目录解压缩。 -``` - -| 使用方式 | 命令 | -| :------: | ----------------------------------------------------- | -| 打包压缩 | tar -jcv -f filename.tar.bz2 要被压缩的文件或目录名称 | -| 查 看 | tar -jtv -f filename.tar.bz2 | -| 解压缩 | tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 | - -## bash - -可以通过 Shell 请求内核提供服务,Bash 正是 Shell 的一种。 - -### 特性 - -- 命令历史:记录使用过的命令 -- 命令与文件补全:快捷键:tab -- shell scripts -- 通配符: 例如 ls -l /usr/bin/X* 列出 /usr/bin 下面所有以 X 开头的文件 - -### 变量操作 - -对一个变量赋值直接使用 =。 - -对变量取用需要在变量前加上 $ ,也可以用 ${} 的形式; - -输出变量使用 echo 命令。 - -```bash -$ x=abc -$ echo $x -$ echo ${x} -``` - -变量内容如果有空格,必须使用双引号或者单引号。 - -- 双引号内的特殊字符可以保留原本特性,例如 x="lang is $LANG",则 x 的值为 lang is zh_TW.UTF-8; -- 单引号内的特殊字符就是特殊字符本身,例如 x='lang is $LANG',则 x 的值为 lang is $LANG。 - -可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如 version=$(uname -r),则 version 的值为 4.15.0-22-generic。 - -可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如 version=$(uname -r),则 version 的值为 4.15.0-22-generic。 - -Bash 的变量可以声明为数组和整数数字。注意数字类型没有浮点数。如果不进行声明,默认是字符串类型。变量的声明使用 declare 命令: - -```bash -$ declare [-aixr] variable --a : 定义为数组类型 --i : 定义为整数类型 --x : 定义为环境变量 --r : 定义为 readonly 类型 -``` - -对数组操作 - -```bash -$ array[1]=a -$ array[2]=b -$ echo ${array[1]} -``` - -## 正则 - -g/re/p(globally search a regular expression and print),使用正则表示式进行全局查找并打印。 - -```bash -$ grep [-acinv] [--color=auto] 搜寻字符串 filename --c : 统计个数 --i : 忽略大小写 --n : 输出行号 --v : 反向选择,也就是显示出没有 搜寻字符串 内容的那一行 ---color=auto : 找到的关键字加颜色显示 -``` - -示例: 把含有 the 字符串的行提取出来(注意默认会有 --color=auto 选项,因此以下内容在 Linux 中有颜色显示 the 字符串) - -```bash -$ grep -n 'the' regular_express.txt -8:I can't finish the test. -12:the symbol '*' is represented as start. -15:You are the best is mean you are the no. 1. -16:The world Happy is the same with "glad". -18:google is the best tools for search keyword -``` - -## 进程管理 - -### 查看进程 - -#### ps - -查看某个时间点的进程信息 - -示例一: 查看自己的进程`ps -l` - -示例二: 查看系统所有进程`ps aux` - -示例三: 查看特定的进程`ps aux | grep python` - -#### top - -实时显示进程信息 - -示例: 两秒钟刷新一次`top -d 2` - -#### pstree - -查看进程树 - -示例: 查看所有进程树`pstree -A` - -#### netstat - -查看占用端口的进程 - -示例: 查看特定端口的进程`netstat -anp | grep port` - -### 孤儿进程 - -一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。 - -孤儿进程将被init进程(进程号为1)所收养,并有init进程对它们完成状态收集工作。 - -由于孤儿进程会被init进程收养,所以孤儿进程不会对系统造成危害。 - -### 僵尸进程 - -一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过wait()或者waitpid()获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用wait()或waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。 - -僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。 - -系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。 - -要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。 \ No newline at end of file diff --git a/Interview/mianjing/.DS_Store b/Interview/mianjing/.DS_Store deleted file mode 100644 index 25f7246f..00000000 Binary files a/Interview/mianjing/.DS_Store and /dev/null differ diff --git a/Interview/mianjing/ali/.DS_Store b/Interview/mianjing/ali/.DS_Store deleted file mode 100644 index 5008ddfc..00000000 Binary files a/Interview/mianjing/ali/.DS_Store and /dev/null differ diff --git a/Interview/mianjing/jingdong/jingdong01.md b/Interview/mianjing/jingdong/jingdong01.md deleted file mode 100644 index abe66525..00000000 --- a/Interview/mianjing/jingdong/jingdong01.md +++ /dev/null @@ -1,190 +0,0 @@ -## 京东Java实习一面电话面 -- [原链接](https://www.nowcoder.com/discuss/414511) - -### Java集合中线程安全的有哪些? -- Vector->SynchronizedList->CopyOnWriteArrayList -- SynchronizedSet->CopyOnWriteArraySet -- SynchronizedMap->ConcurrentHashMap - -### 线程的创建方式 -- 继承Thread类创建线程 -```java -public class ThreadTest extends Thread{ - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - for(int i=0;i<5;i++){ - //使用线程的getName()方法可以直接获取当前线程的名称 - System.out.println(this.getName() + " " + i); - } - } - - public static void main(String[] args){ - //输出Java程序运行时默认运行的主线程名称 - System.out.println(Thread.currentThread().getName()); - //创建第一个线程并开始执行 - new ThreadTest().start(); - //创建第二个线程并开始执行 - new ThreadTest().start(); - } -} -``` -- 实现Runnable接口创建线程 -```java -public class ThreadTest implements Runnable{ - private int i; - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - //实现Runnable接口时,只能使用如下方法获取线程名 - System.out.println(Thread.currentThread().getName() + " " + i); - i++; - } - - public static void main(String[] args){ - ThreadTest tt = new ThreadTest(); - //创建第一个线程并开始执行 - //输出 新线程1 0 - new Thread(tt,"新线程1").start(); - //创建第二个线程并开始执行 - //输出 新线程2 1 - new Thread(tt,"新线程2").start(); - //使用Lambda表达式创建Runnable对象 - new Thread(()->{ - System.out.print("hello"); - System.out.println("'world"); - }).start(); - } -} -``` -- 使用Callable和Future创建线程 -```java -public class ThreadTest { - public static void main(String[] args){ - //使用FutureTask来包装Callable对象 - //使用Lambda表达式来创建Callable对象 - FutureTask task = new FutureTask<>(()->{ - System.out.println(Thread.currentThread().getName() + " " + "开始执行任务!"); - return 0; - }); - //实质还是以Callable对象来创建并启动线程 - //输出 新线程 开始执行任务! - new Thread(task,"新线程").start(); - try{ - //获取线程的返回值 - //输出 0 - System.out.print(task.get()); - }catch (Exception ex){ - ex.printStackTrace(); - } - - } -} -``` -- 使用线程池例如用Executor框架 -```java -public class ThreadTest { - public static void main(String[] args){ - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(()->{ - System.out.println(Thread.currentThread().getName()); - }); - } -} -``` - -### 堆排序的实现过程(唯一的一道算法题) -```java -public class HeapSort { - public static void heapSort(int[] arr){ - if (arr == null || arr.length < 2) return; - for (int i = 0; i < arr.length; i++) { - heapInsert(arr, i); // 依次从0~i形成大根堆 - } - int heapSize = arr.length; - swap(arr, 0, --heapSize); - while (heapSize > 0) { - heapify(arr, 0, heapSize); - swap(arr, 0, --heapSize); - } - } - - public static void heapInsert(int[] arr, int index) { - // 建立大根堆 - while (arr[index] > arr[(index - 1) / 2]) { - swap(arr, index, (index - 1) / 2); - index = (index - 1) / 2; - } - } - - public static void heapify(int[] arr, int index, int heapSize) { - // 调整成大根堆 - int left = index * 2 + 1; - while (left < heapSize) { - int largest = left + 1 < heapSize && arr[left + 1] > arr[left] - ? left + 1 - : left; - largest = arr[largest] > arr[index] ? largest : index; - if (largest == index) { - break; // 自己已经是最大了,直接跳出 - } - swap(arr, largest, index); - index = largest; - left = index * 2 + 1; - } - } - - public static void swap(int[] arr, int x, int y) { - int temp = arr[x]; - arr[x] = arr[y]; - arr[y] = temp; - } -} -``` -![HeapSort](http://media.dreamcat.ink/uPic/HeapSort.png) - - -### 垃圾回收算法 各自优缺点 -- 标记-清除 -> 该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: -1. 效率问题 -2. 空间问题(标记清除后会产生大量的不连续碎片) - -- 标记-整理 -> 根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 - -1. 慢 -2. 解决了碎片 - -- 复制 -> 为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 - -1. 快 - -- 分代收集 -> 比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。 - -1. 根据特点选择不同场景下的收集算法,更加灵活。 - -### 数据库的三大范式 -#### 第一范式 -在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 -所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。 -如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,第一范式就是无重复的列。 - -#### 第二范式 -第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。 -第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。 -为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 -第二范式(2NF)要求实体的属性完全依赖于主关键字。 -所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。 -为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。 - -### 第三范式 -满足第三范式(3NF)必须先满足第二范式(2NF)。 -简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。 -例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。 -那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。 -如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) - -### 对象存储为json文件,不借助第三方jar包,说说思路。 -递归迭代 -- [参考](https://blog.csdn.net/qq_35893120/article/details/84288551?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4) diff --git a/Interview/mianjing/jingdong/jingdong02.md b/Interview/mianjing/jingdong/jingdong02.md deleted file mode 100644 index 27e6b438..00000000 --- a/Interview/mianjing/jingdong/jingdong02.md +++ /dev/null @@ -1,299 +0,0 @@ -## 京东java实习面经 -- [原文链接](https://www.nowcoder.com/discuss/419373) - -## 一面 -### redis为什么可以这么快,持久化机制 -Redis性能如此高的原因,我总结了如下几点: -- 纯内存操作 -- 单线程 -- 高效的数据结构 -- 合理的数据编码 -- 其他方面的优化 - -在 Redis 中,常用的 5 种数据结构和应用场景如下: -- **String**:缓存、计数器、分布式锁等。 -- **List**:链表、队列、微博关注人时间轴列表等。 -- **Hash**:用户信息、Hash 表等。 -- **Set**:去重、赞、踩、共同好友等。 -- **Zset**:访问量排行榜、点击量排行榜等。 - -[https://zhuanlan.zhihu.com/p/57089960](https://zhuanlan.zhihu.com/p/57089960) - -### redis持久化机制 -Redis支持持久化,而且支持两种不同的持久化操作 -- **快照**(snapshotting,RDB) -- **只追加文件**(append-only file,AOF) - -#### RDB -> Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。 - -#### AOF -> 与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。 -``` -appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 -appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 -appendfsync no #让操作系统决定何时进行同步 -``` - -#### Redis4.0 -> Redis 4.0 开始支持 RDB 和 AOF 的混合持久化 - -### spring中的ioc运行机制 -```java -public void refresh() throws BeansException, IllegalStateException { - // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 - synchronized (this.startupShutdownMonitor) { - - // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 - prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean - // 这块待会会展开说 - prepareBeanFactory(beanFactory); - - try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 - // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - postProcessBeanFactory(beanFactory); - // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - invokeBeanFactoryPostProcessors(beanFactory); - - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 - // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization - // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - registerBeanPostProcessors(beanFactory); - - // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 - initMessageSource(); - - // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 - initApplicationEventMulticaster(); - - // 从方法名就可以知道,典型的模板方法(钩子方法), - // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - onRefresh(); - - // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 - registerListeners(); - - // 重点,重点,重点 - // 初始化所有的 singleton beans - //(lazy-init 的除外) - finishBeanFactoryInitialization(beanFactory); - - // 最后,广播事件,ApplicationContext 初始化完成 - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // 把异常往外抛 - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -``` -![spring-ioc](http://media.dreamcat.ink/uPic/spring-ioc.png) - -#### 大概总结 -1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; - 1. xml注册bean - 2. 注解注册bean;@Service、@Component、@Bean,xxx - 3. Spring容器会在合适时机创建这些Bean - 1. 用到这个bean的创建;利用getBean创建bean;创建好以后保存在容器中。 - 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); - 4. 后置处理器;BeanPostProcessor - 1. 每一个bean创建完成,都会使用各种后置处理器进行处理,来增强bena的功能,比如: - - AutowiredAnnotationBeanPostProcessor:处理自动注入 - - AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; - - xxx - 5. 事件驱动模型: - - ApplicationListener:事件监听; - - ApplicationEventMulticaster:事件派发; - -- [http://dreamcat.ink/2020/01/31/spring-springaop-yuan-ma-fen-xi/](http://dreamcat.ink/2020/01/31/spring-springaop-yuan-ma-fen-xi/) -- [https://javadoop.com/post/spring-ioc](https://javadoop.com/post/spring-ioc) - -### spring中aop的原理 -> AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 -> Spring AOP就是基于动态代理的 - -大致流程: -1. @EnableAspectJAutoProxy 开启AOP功能 -2. @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator -3. AnnotationAwareAspectJAutoProxyCreator是一个后置处理器 -4. 容器的创建流程: - 1. registerBeanPostProcessors()注册后置处理器,创建AnnotationAwareAspectJAutoProxyCreator对象 - 2. finishBeanFactoryInitialization()初始化剩下的单实例bean - 1. 创建业务逻辑组件和切面组件 - 2. AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 - 3. 组件创建完之后,判断组件是否需要增强,如果是:切面的通知方法,包装成增强器(advisor),给业务逻辑组件创建一个代理对象(cglib) -5. 执行目标方法: - 1. 代理对象执行目标方法 - 2. CglibAopProxy.intercept() - 1. 得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) - 2. 利用拦截器的链式机制,依次进入每一个拦截器进行执行 - 3. 效果: - - 正常执行:前置通知->目标方法->后置通知->返回通知 - - 出现异常:前置通知->目标方法->后置通知->异常通知 - -### spring中的单例模式和设计模式中的单例模式有什么区别 -- 单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。 -- 意味着: - - 在一个JVM进程中(理论上,一个运行的JAVA程序就必定有自己一个独立的JVM)仅有一个实例,于是无论在程序中的何处获取实例,始终都返回同一个对象。 - - 与此相比,Spring的单例Bean是与其容器(ApplicationContext)密切相关的,所以在一个JVM进程中,如果有多个Spring容器,即使是单例bean,也一定会创建多个实例。 - -## 二面 - -### redis为什么这么快 -略 - -### 介绍redis运行机制 -> redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。 - -文件事件处理器的结构包含 4 个部分: -- 多个 socket -- IO多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) - -多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,会将socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。 - -### hashmap多线程下安全问题,如何解决 -比如死循环分析: -- [https://zhuanlan.zhihu.com/p/67915754](https://zhuanlan.zhihu.com/p/67915754) - -### wait()和sleep()区别 -- 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁**。 -- 两者都可以暂停线程的执行。 -- wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。 -- wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify或者notifyAll方法。sleep方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。 - -### synchronized和volatile区别 -synchronized的修饰范围: -- 修饰一个代码块 -- 修饰一个方法 -- 修饰一个类 -- 修饰一个静态的方法 - -> 个人的理解是:因为同步关键字Synchronized不能修饰变量(不能直接使用synchronized声明一个变量),不能使变量得到共享,故引入了轻量级的Volatie - -volatile: -- volatile可以修饰变量,共享变量。 -- 保障了共享变量对所有线程的可见性。即可保证在线程A将其修改时,线程B可以立刻得到。 -- 禁止指令重排序 - -### 线程池的参数定义,大小 -先放一波变量定义 -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` -![线程池参数](http://media.dreamcat.ink/uPic/线程池参数.png) - -ThreadPoolExecutor 3 个最重要的参数: -- `corePoolSize`:核心线程数定义了最小可以同时运行的线程数量。 -- `maximumPoolSize`:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- `workQueue`:当新任务来的时候会先判断当前运行的线程数量是否达到了核心线程数,如果达到的话,信任就会被从存放到队列中中。 - -ThreadPoolExecutor 其他常见参数: -- `keepAliveTime`:当线程池中的线程数量大于`corePoolSize`的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了`keepAliveTime`才会被回收销毁 -- `unit`:`keepAliveTime`参数的时间单位 -- `threadFactory`:executor创建新线程的时候会用到。 -- `handle`:饱和策略。关于饱和策略下面单独介绍 -如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor`定义一些策略: -- `ThreadPoolExecutor.AbortPolicy`:抛出`RejectedExecutionException`来拒绝新任务的处理。 -- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- `ThreadPoolExecutor.DiscardPolicy`: 不处理新任务,直接丢弃掉。 -- `ThreadPoolExecutor.DiscardOldestPolicy`: 此策略将丢弃最早的未处理的任务请求。 - -大小: -> 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。 -> 但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。 - -- **CPU 密集型任务(N+1)**: 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。 -- **I/O 密集型任务(2N)**: 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。 - -### java为何设计成单继承 -可以举个例子: -> 在这里有个A类,我们又编写了两个类B类和C类,并且B类和C类分别继承了A类,并且对A类的同一个方法进行了覆盖。 -> 如果此时我们再次编写了一个D类,并且D类以多继承的方式同时集成了B类和C类,那么D类也会继承B类和C类从A类中重载的方法,那么问题来了,D类也开始犯迷糊了,我到底应该哪个继承哪个类中的方法呢, -> 因为类是结构性的,这样就会造成结构上的混乱。这就是多继承的菱形继承问题。 - -可以实现对个接口: -> Java接口是行为性的,也就是说它只是定义某个行为的名称,而具体的行为的实现是集成接口的类实现的, -> 因此就算两个接口中定义了两个名称完全相同的方法,当某个类去集成这两个接口时, -> 类中也只会有一个相应的方法,这个方法的具体实现是这个类来进行编写的,所以并不会出现结构混乱的情况。 - -### 继承和组合有啥区别 -继承: -> 继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。 - -- 父类的内部细节对子类是可见的。 -- 子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。 -- 子类与父类是一种高耦合,违背了面向对象思想。 -- 继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了子类的可维护性。 -- 不支持动态继承。在运行时,子类无法选择不同的父类。 - -组合: -- 不破坏封装,整体类与局部类之间松耦合,彼此相对独立。 -- 具有较好的可扩展性。 -- 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象。 - -组合是has a的关系, 继承是is a的关系 -> 引用一句老话应该更能分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。 - -总结: -- 除非考虑使用多态,否则优先使用组合。 -- 要实现类似”多重继承“的设计的时候,使用组合。 -- 要考虑多态又要考虑实现“多重继承”的时候,使用组合+接口。 - - diff --git a/Interview/mianjing/jingdong/jingdong03.md b/Interview/mianjing/jingdong/jingdong03.md deleted file mode 100644 index c5c2cf8e..00000000 --- a/Interview/mianjing/jingdong/jingdong03.md +++ /dev/null @@ -1,196 +0,0 @@ -## 京东一,二面面经,许愿offer!!! -- [原文](https://www.nowcoder.com/discuss/419687) - -### hashmap数据结构 -#### put -**1.7** -直接上图: -![hashmap-put-1.7](http://media.dreamcat.ink/uPic/hashmap-put-1.7.png) -**1.8** -直接上图: -![hashmap-put-1.8](http://media.dreamcat.ink/uPic/hashmap-put-1.8.png) - -#### 100个结点,hashmap初始容量多少合适? -> Map在使用过程中不断的往里面存放数据,当数量达到了16 * 0.75 = 12就需要将当前16的容量进行扩容,而扩容这个过程涉及到rehash(重新哈希)、复制数据等操作,所有非常消耗性能。 - -#### 为什么1.8要引入红黑树,红黑树是平衡树吗? -- 当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)。 -- 是 - -#### 红黑树为什么要分红节点和黑节点? -> 红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多 - -### JVM的内存模型 -1.8之前: -![JVM内存模型-1.8之前](http://media.dreamcat.ink/uPic/JVM内存模型-1.8之前.png) - -1.8: -![JVM内存模型-1.8](http://media.dreamcat.ink/uPic/JVM内存模型-1.8.png) - -按照1.8总结: -**线程私有**: -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -**线程共享**: -- 堆 -- 直接内存 - -**程序计数器**: -- 字节码解释器通过改变程序计数器来一次读取命令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道线程上次运行到哪儿。 - -> 注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束儿死亡。 - -**Java虚拟机栈**: -> 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 - -Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分: -- 局部变量表 -- 操作数栈: - - 8大基本类型 - - 对象引用:可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 动态链接 -- 方法出口 - -Java虚拟机栈会出现两种异常:`StackOverFlowError`和`OutOfMemoryError`。 -- `StackOverFlowError`:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出`StackOverFlowError`异常。 -- `OutOfMemoryError`:若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛去`OutOfMemoryError`异常。 - -**本地方法栈**: -> 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 其他和Java虚拟机差不多的 - -**堆**: -> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 -> Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -![堆内部分区](https://user-gold-cdn.xitu.io/2020/3/31/17130c2c0c80892e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) -上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。 -大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。 -对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。经过这次GC后,Eden区和"From"区已经被清空。 -这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。 -不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。 - -## 二面 -### Thread和Runnable开启线程有什么区别? -- 避免了Java单继承的局限性; -- 把线程代码和任务的代码分离,解耦合(解除线程代码和任务的代码模块之间的依赖关系)。代码的扩展性非常好; - -### 线程池 -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` -![线程池参数](http://media.dreamcat.ink/uPic/线程池参数.png) - -`ThreadPoolExecutor` 3 个最重要的参数: - -- `corePoolSize` : 核心线程数线程数定义了最小可以同时运行的线程数量。 -- `maximumPoolSize` : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- `workQueue`: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: -- `keepAliveTime`:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁; -- `unit` : `keepAliveTime` 参数的时间单位。 -- `threadFactory` :`executor` 创建新线程的时候会用到。 -- `handler` :饱和策略。关于饱和策略下面单独介绍一下. - -`ThreadPoolTaskExecutor` 定义一些策略: -- `ThreadPoolExecutor.AbortPolicy`:抛出 `RejectedExecutionException来拒绝新任务的处理`。 -- `ThreadPoolExecutor.CallerRunsPolicy`:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- `ThreadPoolExecutor.DiscardPolicy`: 不处理新任务,直接丢弃掉。 -- `ThreadPoolExecutor.DiscardOldestPolicy`: 此策略将丢弃最早的未处理的任务请求。 - -### new对象时的过程 -![Java创建对象的过程](http://media.dreamcat.ink/uPic/Java创建对象的过程.png) - -1. 类加载检查,虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 -2. 分配内存,在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 -3. 初始化零值,内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 -4. 设置对象头,初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 -5. 执行init方法,在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - -**内存布局**: -在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域: -- 对象头 -- 实例数据 -- 对齐填充 -Hotspot 虚拟机的对象头包括两部分信息 -- 第一部分用于存储对象自身的自身运行时数据(哈希码、GC 分代年龄、锁状态标志等等) -- 另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 -实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。 - -> 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 - -**对象的访问方式**: -建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有 -- 使用句柄 -- 直接指针 - -### mysql的优化?索引??联合索引?? -#### 索引 -- 普通索引:仅加速查询 -- 唯一索引:加速查询+列值唯一(可以有null) -- 主键索引:加速查询+列值唯一(不可以有null)+表中只有一个 -- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -- 全文索引:对文本的内容进行分词,进行搜索 -- 索引合并:使用多个单列索引组合搜索 -- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖 - -#### 优化 -**索引优化**: -- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性 -- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因 -- 帮助服务器避免排序和临时表 -- 将随机IO变为顺序IO -- 可以加速表和表之间的连接,特别是咋实现数据的参考完整性方面的特别喲意义 - -**数据库表优化** -- 范式优化:比如消除冗余 -- 反范式优化:比如适当加冗余等(减少join) -- 限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。 -- 读/写分离:经典的数据库拆分方案,主库负责写,从库负责读 -- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间。 - -**联合索引** -> MySQL中的索引可以以一定顺序引用多个列,这种索引叫做联合索引一般的,一个联合索引是一个有序元组,其中各个元素均为数据表的一列。另外,单列索引可以看成联合索引元素数为1的特例。 - -索引匹配的最左原则具体是说,假如索引列分别为A,B,C,顺序也是A,B,C: -``` -- 那么查询的时候,如果查询【A】【A,B】 【A,B,C】,那么可以通过索引查询 -- 如果查询的时候,采用【A,C】,那么C这个虽然是索引,但是由于中间缺失了B,因此C这个索引是用不到的,只能用到A索引 -- 如果查询的时候,采用【B】 【B,C】 【C】,由于没有用到第一列索引,不是最左前缀,那么后面的索引也是用不到了 -- 如果查询的时候,采用范围查询,并且是最左前缀,也就是第一列索引,那么可以用到索引,但是范围后面的列无法用到索引 -``` - -### 5L,3L的杯子,如何装4L的水?? -5L,3L的杯子,如何装4L的水: -- 5L 桶装满水 -- 由 5L 桶向 3L 桶倒水 , 倒满为止 -- 把 3L 桶的水倒掉 , 由 5L 桶向 3L 桶倒水 -- 把 5L 桶装满 -- 由 5L 桶向 3L 桶倒水 , 倒满为止 diff --git a/Interview/mianjing/jingdong/jingdong04.md b/Interview/mianjing/jingdong/jingdong04.md deleted file mode 100644 index fbc667cc..00000000 --- a/Interview/mianjing/jingdong/jingdong04.md +++ /dev/null @@ -1,717 +0,0 @@ -## 京东Java春季实习 -- [原文链接](https://www.nowcoder.com/discuss/413662) - -## 一面 - -### hashcode和equals方法 -#### == -> 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。 - -#### equals -> 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: -- 情况1:类没有覆盖equals方法。则通过equals比较该类的两个对象时,等价于通过"=="比较这两个对象。 -- 情况2:类覆盖了equals方法。一般,我们都覆盖了equals方法来比较两个对象的内容呢是否相相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 -- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 -- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前你引用。如果没有就在常量池中重新创建哪一个String对象。 - -#### hashcode -> 它的作用是获取哈希吗,也称为散列码,它实际上是返回一个int整数。这个哈希吗的作用是确定该对象在哈希表中的索引位置。hashcode定义在jdk的object.java中,这就意味着Java中的任何类都包涵hashcode函数。 - -#### 为什么要用hashcode -我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: -> 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较, -> 如果没有相符的hashcode,HashSet会假设对象没有重复出现。 -> 但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。 -> 如果两者相同,HashSet 就不会让其加入操作成功。 -> 如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - -### 接口和抽象类的区别 -1. 接口的方法默认是public,所有方法在接口中不能有实现(除了java8),而抽象类可以有非抽象的方法。 -2. 接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。 -3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过implement关键字扩展多个接口。 -4. 接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰) -5. 从设计层面来讲,抽象是对类的抽象,是一种能模板设计,而接口是对行为的抽象, 是一种行为的规范。 - -### 重写和重载的区别 - -#### 重载 -> 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 - -#### 重写 -> 重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。 -> 另外,如果父类方法访问修饰符为private则子类就不能重写该方法。 -> 也就是说方法提供的行为改变,而方法的外貌并没有改变。 - -### 集合类有哪些,有什么区别 -> 容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。 - -#### Set -- TreeSet -> 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet查找的时间复杂为O(1),TreeSet则为O(logN)。 - -- HashSet -> 基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用Iterator遍历HashSet得到的结构是不确定的。 - -- LinkedHashSet -> 具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。 - -#### List -- ArrayList -> 基于动态数组实现,支持随机访问。 - -- Vector -> 和ArrayList类似,但它是线程安全的。 - -- LinkedList -> 基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈、队列和双向队列。 - -#### Queue -- LinkedList -> 可以用来实现双向队列。 - -- PriorityQueue -> 基于堆结构实现,可以用它实现优先队列。 - -#### Map -- TreeMap -> 基于红黑树实现 - -- HashMap -> 基于哈希表实现 - -- HashTable - - 和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。 - - 它是遗留类,不应该去使用它。 - - 现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁(1.8前)。 - -- LinkedHashMap -> 使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。 - -### 如何实现多线程 -- 继承Thread类创建线程 -```java -public class ThreadTest extends Thread{ - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - for(int i=0;i<5;i++){ - //使用线程的getName()方法可以直接获取当前线程的名称 - System.out.println(this.getName() + " " + i); - } - } - - public static void main(String[] args){ - //输出Java程序运行时默认运行的主线程名称 - System.out.println(Thread.currentThread().getName()); - //创建第一个线程并开始执行 - new ThreadTest().start(); - //创建第二个线程并开始执行 - new ThreadTest().start(); - } -} -``` -- 实现Runnable接口创建线程 -```java -public class ThreadTest implements Runnable{ - private int i; - //重写run()方法,run()方法的方法体是线程执行体 - public void run(){ - //实现Runnable接口时,只能使用如下方法获取线程名 - System.out.println(Thread.currentThread().getName() + " " + i); - i++; - } - - public static void main(String[] args){ - ThreadTest tt = new ThreadTest(); - //创建第一个线程并开始执行 - //输出 新线程1 0 - new Thread(tt,"新线程1").start(); - //创建第二个线程并开始执行 - //输出 新线程2 1 - new Thread(tt,"新线程2").start(); - //使用Lambda表达式创建Runnable对象 - new Thread(()->{ - System.out.print("hello"); - System.out.println("'world"); - }).start(); - } -} -``` -- 使用Callable和Future创建线程 -```java -public class ThreadTest { - public static void main(String[] args){ - //使用FutureTask来包装Callable对象 - //使用Lambda表达式来创建Callable对象 - FutureTask task = new FutureTask<>(()->{ - System.out.println(Thread.currentThread().getName() + " " + "开始执行任务!"); - return 0; - }); - //实质还是以Callable对象来创建并启动线程 - //输出 新线程 开始执行任务! - new Thread(task,"新线程").start(); - try{ - //获取线程的返回值 - //输出 0 - System.out.print(task.get()); - }catch (Exception ex){ - ex.printStackTrace(); - } - - } -} -``` - -- 使用线程池例如用Executor框架 -```java -public class ThreadTest { - public static void main(String[] args){ - ExecutorService executorService = Executors.newFixedThreadPool(1); - executorService.submit(()->{ - System.out.println(Thread.currentThread().getName()); - }); - } -} -``` - -### JVM内存分布 -#### 1.8之前 -![JVM内存模型-1.8之前](http://media.dreamcat.ink/uPic/JVM内存模型-1.8之前.png) - -#### 1.8及之后 -![JVM内存模型-1.8](http://media.dreamcat.ink/uPic/JVM内存模型-1.8.png) - -#### 大概总结 -线程私有: -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -线程共享: -- 堆 -- 直接内存 - -##### 程序计数器 -- 字节码解释器通过改变程序计数器来一次读取命令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道线程上次运行到哪 -> 注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 - -##### Java虚拟机栈 -> 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 - -Java内存可以粗糙的区分为堆内存和栈内存,其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中的局部变量表部分: -- 局部变量表 -- 操作数栈: - - 8大基本类型 - - 对象引用:可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置 -- 动态链接 -- 方法出口 - -Java虚拟机栈会出现两种异常:`StackOverFlowError`和`OutOfMemoryError`。 -- `StackOverFlowError`:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的祖达深度的时候,就抛出`StackOverFlowError`异常。 -- `OutOfMemoryError`:若Java虚拟机栈的内存大小允许动态扩展,且线程请求栈时内存用完了,无法再动态扩展了,此时抛出`OutOfMemoryError`异常。 - -##### 本地方法栈 -> 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 其他和Java虚拟机差不多的 - -##### 堆 -> Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 - Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 - -![堆内存分区](http://media.dreamcat.ink/uPic/堆内存分区.png) - -- 上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。 -- 大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。 -- 对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。经过这次GC后,Eden区和"From"区已经被清空。 -- 这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。 -- 不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。 - -### 讲一讲Spring原理 -谈Spring原来,不得不谈Ioc了 -> IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC在其他语言中也有应用,并非Spring持有。 -> IoC容器是Spring用来实现IoC的载体,IC容器实际上就是个Map(key,value),Map中存放的是各种对象。 - -将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。 -这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 -IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 -在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。 -如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 - -#### Spring中的ioc运行机制 -```java -public void refresh() throws BeansException, IllegalStateException { - // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛 - synchronized (this.startupShutdownMonitor) { - - // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符 - prepareRefresh(); - - // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中, - // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了, - // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map) - ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - - // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean - // 这块待会会展开说 - prepareBeanFactory(beanFactory); - - try { - // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口, - // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】 - - // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化 - // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事 - postProcessBeanFactory(beanFactory); - // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法 - invokeBeanFactoryPostProcessors(beanFactory); - - // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别 - // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization - // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化 - registerBeanPostProcessors(beanFactory); - - // 初始化当前 ApplicationContext 的 MessageSource,国际化这里就不展开说了,不然没完没了了 - initMessageSource(); - - // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了 - initApplicationEventMulticaster(); - - // 从方法名就可以知道,典型的模板方法(钩子方法), - // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前) - onRefresh(); - - // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过 - registerListeners(); - - // 重点,重点,重点 - // 初始化所有的 singleton beans - //(lazy-init 的除外) - finishBeanFactoryInitialization(beanFactory); - - // 最后,广播事件,ApplicationContext 初始化完成 - finishRefresh(); - } - - catch (BeansException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Exception encountered during context initialization - " + - "cancelling refresh attempt: " + ex); - } - - // Destroy already created singletons to avoid dangling resources. - // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 - destroyBeans(); - - // Reset 'active' flag. - cancelRefresh(ex); - - // 把异常往外抛 - throw ex; - } - - finally { - // Reset common introspection caches in Spring's core, since we - // might not ever need metadata for singleton beans anymore... - resetCommonCaches(); - } - } -} -``` -![spring-ioc](http://media.dreamcat.ink/uPic/spring-ioc.png) - -大概总结: -1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; - 1. xml注册bean - 2. 注解注册bean;@Service、@Component、@Bean,xxx - 3. Spring容器会在合适时机创建这些Bean - 1. 用到这个bean的创建;利用getBean创建bean;创建好以后保存在容器中。 - 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); - 4. 后置处理器;BeanPostProcessor - 1. 每一个bean创建完成,都会使用各种后置处理器进行处理,来增强bena的功能,比如: - - AutowiredAnnotationBeanPostProcessor:处理自动注入 - - AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; - 5. 事件驱动模型: - - ApplicationListener:事件监听; - - ApplicationEventMulticaster:事件派发; - - -### mysql的引擎,索引结构,索引有哪些类型? -#### 数据库引擎innodb与myisam的区别 -InnoDB: -- 是MySQL默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其他存储引擎。 -- 实现了四个标准的隔离级别,默认级别是可重复读。在可重复读隔离级别下,通过多版本并发控制(MVCC)+间隙锁(Next-Key Locking)防止幻影读。 -- 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 -- 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。 -- 支持真正的在线热备份。其他存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。 - -MyISAM: -- 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括压缩表、空间数据索引等。 -- 不支持事务。 -- 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 - -#### 索引类型 -- FULLTEXT - 即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 -- HASH - 由于HASH的唯一(几乎100%的唯一)及类似键值对的形式,很适合作为索引。 - HASH索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效,对于范围查询、排序及组合索引仍然效率不高。 -- BTREE - BTREE索引就是一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf。这是MySQL里默认和最常用的索引类型。 -- RTREE - RTREE在MySQL很少使用,仅支持geometry数据类型,支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。 - 相对于BTREE,RTREE的优势在于范围查找。 - -#### B+树 -B+ 树是一种树数据结构,通常用于关系型数据库(如Mysql)和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。 -在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 -b+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少。 - -### mysql 的共享锁和排它锁 - -#### 共享锁 -> 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 - -#### 排它锁 -> 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。 - -### mybatis如何防止sql注入 -**首先看一下下面两个sql语句的区别:** -```mysql - `标签,都会被解析为一个MapperStatement对象。** + +**Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。** + +## Mybatis是如何进行分页的?分页插件的原理是什么? + +**Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。** + +**分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。** + +## Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式? + +1. 第一种是使用` `标签,逐一定义数据库列名和对象属性名之间的映射关系。 +2. 第二种是使用sql列的别名功能,将列的别名书写为对象属性名。 + +**有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。** + +## **如何获取自动生成的(主)键值?** + +- insert 方法总是返回一个int值 ,这个值代表的是插入的行数。 +- 如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。 + +## 在mapper中如何传递多个参数? + +1. DAO层的函数 +2. 使用@param注解 +3. 多个参数封装成map + +## Mybatis动态sql有什么用?执行原理?有哪些动态sql? + +**Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。** + +## Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签? + +**`` ,加上动态sql的9个标签,其中 ` `为sql片段标签,通过 ` `标签引入sql片段, ` `为不支持自增的主键生成策略标签。** + +## Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复? + +**不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;** + +**原因就是namespace+id是作为Map 的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。** + +## 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? + +**Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。** + +## MyBatis实现一对一有几种方式?具体怎么操作的? + +**有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;** + +**嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。** + +## MyBatis实现一对多有几种方式,怎么操作的? + +**有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。** + +## Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? + +**Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。** + +**它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。** + +## Mybatis的一级、二级缓存: + +- 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。 +- 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ; +- 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。 + +## 什么是MyBatis的接口绑定?有哪些实现方式? + +接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定, 我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。 + +接口绑定有两种实现方式: + +- 注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定; +- 外一种就是通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。 + +## 使用MyBatis的mapper接口调用时有哪些要求? + +- Mapper接口方法名和mapper.xml中定义的每个sql的id相同; +- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同; +- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同; +- Mapper.xml文件中的namespace即是mapper接口的类路径。 \ No newline at end of file diff --git a/Java/spring-books/README.md b/Java/spring-books/README.md new file mode 100644 index 00000000..d0164050 --- /dev/null +++ b/Java/spring-books/README.md @@ -0,0 +1,73 @@ +# Spring书籍、源码分析与学习 +## 引言 +> 个人Spring学习之旅,📝笔记。 + +## 参考笔记 +- [b站视频入口](https://www.bilibili.com/video/av32102436?p=1) +- [代码资料](https://gitee.com/adanzzz/spring_source_parsing_data) +## 思维导图 +- [Spring注解驱动开发](https://www.processon.com/view/link/5e30213ae4b096de64c8e9bf) + + +## 目录说明 +### 组件注册 +- [xml注册bean方式](docs/1.xml注解bean方式.md) +- [xml注册包扫描方式](docs/2.xml注册包扫描方式.md) +- [注解注册bean方式](docs/3.注解注册bean方式.md) +- [注解注册包扫描方式](docs/4.注解注册包扫描方式.md) +- [@Filter的使用](docs/5.@Filter的使用.md) +- [@Scope的使用](docs/6.@Scope的使用.md) +- [@Lazy的使用](docs/7.@Lazy的使用.md) +- [@Conditional的使用](docs/8.@Conditional的使用.md) +- [@Import的使用](docs/9.@Import的使用.md) +### 生命周期 +- [bean的生命周期](docs/10.bean的生命周期.md) +### 属性赋值 +- [@Value的使用](docs/11.@Value的使用.md) +- [@PropertySource的使用](docs/12.@PropertySource的使用.md) +### 自动装配 +- [@Autowired的使用](docs/13.@Autowired的使用.md) +- [@Qualifier的使用](docs/14.@Qualifier的使用.md) +- [@Profile的使用](docs/15.@Profile的使用.md) +### AOP +- [AOP小例子](docs/16.AOP小例子.md) +### 事务 +- [Spring事务](docs/17.Spring事务.md) +- [Spring事务源码](docs/17.Spring事务.md) +### 扩展原理 +- [BeanFactoryPostProcessor](docs/18.BeanFactoryPostProcessor.md) +- [BeanDefinitionRegistryPostProcessor](docs/19.BeanDefinitionRegistryPostProcessor.md) +- [ApplicationListener](docs/20.ApplicationListener.md) +## 源码总结 +- [Spring和SpringAOP](Spring和SpringAOP源码总结.md) +- [参考这位大佬的MVC原理](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringMVC-Principle) **这位大佬总结的不错,可参考** +- [SpringMVC开发文档](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html) **这里就不贴视频中的SpringMVC工程** +## 手写简单的IOC和AOP +- [手写简单的IOC](spring-ioc) 非常简单,每行都有注释 +- [手写简单的AOP](spring-aop2) 非常简单,每行都有注释 + +## SpringBoot +**[参考这位大佬](https://snailclimb.gitee.io/springboot-guide/#/)** +**项目结构过于具体简单的文件解释就不说了,主要是看细节和原理** +- [SpringBoot启动流程分析](SpringBoot启动流程分析.md) + + +## Spring常见面试问题 +- [参考这位大佬总结的,挺好的](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions) 其实专注一个参考资料,认真备面就完全ok +- [什么是Spring框架](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_1-%e4%bb%80%e4%b9%88%e6%98%af-spring-%e6%a1%86%e6%9e%b6) +- [列举一些重要的Spring模块](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_2-%e5%88%97%e4%b8%be%e4%b8%80%e4%ba%9b%e9%87%8d%e8%a6%81%e7%9a%84spring%e6%a8%a1%e5%9d%97%ef%bc%9f) +- [@RestController vs @Controller](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_3-restcontroller-vs-controller) +- **@ResponseBody的作用**:**注解的作用是将 Controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。** +- [谈谈自己对于Spring IoC和AOP的理解](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_41-%e8%b0%88%e8%b0%88%e8%87%aa%e5%b7%b1%e5%af%b9%e4%ba%8e-spring-ioc-%e5%92%8c-aop-%e7%9a%84%e7%90%86%e8%a7%a3) +- [SpringAOP和AspectAOP有什么区别](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_41-%e8%b0%88%e8%b0%88%e8%87%aa%e5%b7%b1%e5%af%b9%e4%ba%8e-spring-ioc-%e5%92%8c-aop-%e7%9a%84%e7%90%86%e8%a7%a3) +- [Spring中的bean的作用域有哪些](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_51-spring-%e4%b8%ad%e7%9a%84-bean-%e7%9a%84%e4%bd%9c%e7%94%a8%e5%9f%9f%e6%9c%89%e5%93%aa%e4%ba%9b) +- [Spring中的单例bean的线程安全问题了解吗](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_52-spring-%e4%b8%ad%e7%9a%84%e5%8d%95%e4%be%8b-bean-%e7%9a%84%e7%ba%bf%e7%a8%8b%e5%ae%89%e5%85%a8%e9%97%ae%e9%a2%98%e4%ba%86%e8%a7%a3%e5%90%97%ef%bc%9f) +- [@Component和@bean的区别是什么](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_53-component-%e5%92%8c-bean-%e7%9a%84%e5%8c%ba%e5%88%ab%e6%98%af%e4%bb%80%e4%b9%88%ef%bc%9f) +- [将一个类声明为Spring的bean的注解有哪些](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_54-%e5%b0%86%e4%b8%80%e4%b8%aa%e7%b1%bb%e5%a3%b0%e6%98%8e%e4%b8%baspring%e7%9a%84-bean-%e7%9a%84%e6%b3%a8%e8%a7%a3%e6%9c%89%e5%93%aa%e4%ba%9b) +- [Spring中的bean生命周期-一](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_55-spring-%e4%b8%ad%e7%9a%84-bean-%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f) +- [Spring中的bean生命周期-二](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringBean) +- [说说自己对于SpringMVC了解](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_61-%e8%af%b4%e8%af%b4%e8%87%aa%e5%b7%b1%e5%af%b9%e4%ba%8e-spring-mvc-%e4%ba%86%e8%a7%a3) +- [SpringMVC工作原理了解吗](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_62-springmvc-%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86%e4%ba%86%e8%a7%a3%e5%90%97) +- [Spring框架中用到了哪些设计模型-一](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringInterviewQuestions?id=_7-spring-%e6%a1%86%e6%9e%b6%e4%b8%ad%e7%94%a8%e5%88%b0%e4%ba%86%e5%93%aa%e4%ba%9b%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f%ef%bc%9f) +- [Spring框架中用到了哪些设计模型-二](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/Spring-Design-Patterns) + diff --git "a/Java/spring-books/SpringBoot\345\220\257\345\212\250\346\265\201\347\250\213\345\210\206\346\236\220.md" "b/Java/spring-books/SpringBoot\345\220\257\345\212\250\346\265\201\347\250\213\345\210\206\346\236\220.md" new file mode 100644 index 00000000..d26e92ef --- /dev/null +++ "b/Java/spring-books/SpringBoot\345\220\257\345\212\250\346\265\201\347\250\213\345\210\206\346\236\220.md" @@ -0,0 +1,151 @@ +## 引言 + +> SpringBoot大大简化了Spring项目的配置,虽然很舒服,但是为了面试等,还是要知道里面的细节和原理呀,再此举个HelloWorld的例子 + + +## 声明 + +- 使用Idea分析,并使用Idea创建SpringBoot的工程。 +- 项目结构就不一一详细说了,大家肯定都知道每个文件是干什么的。 +- 这里主要是负责细看其中的原理和细节。 +- **版本:2.2.4.RELEASE** + +## SpringApplication实例 + +**先看源码:** + +```java + public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { + this.sources = new LinkedHashSet(); // 1. + this.bannerMode = Mode.CONSOLE; + this.logStartupInfo = true; + this.addCommandLineProperties = true; + this.headless = true; + this.registerShutdownHook = true; + this.additionalProfiles = new HashSet(); + this.resourceLoader = resourceLoader; + Assert.notNull(primarySources, "PrimarySources must not be null"); + this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); + this.webApplicationType = this.deduceWebApplicationType(); + this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 3. + this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 4. + this.mainApplicationClass = this.deduceMainApplicationClass(); // 5. + } +``` + +- `com.example.helloworld.HelloworldApplication`放入到Set的集合中 +- 判断是否为Web环境:存在(javax.servlet.Servlet && org.springframework.web.context.ConfigurableWebApplicationContext )类 +- 创建并初始化ApplicationInitializer列表 (spring.factories) +- 创建并初始化ApplicationListener列表 (spring.factories) +- 初始化主类mainApplicatioClass (DemoApplication) +- **总结:上面就是SpringApplication初始化的代码,new SpringApplication()没做啥事情 ,主要加载了META-INF/spring.factories 下面定义的事件监听器接口实现类** + +## ConfigurableApplicationContext的run方法 + +**看源码,都可以找的到** + +```java +public ConfigurableApplicationContext run(String... args) { + StopWatch stopWatch = new StopWatch(); // 1. + stopWatch.start(); + ConfigurableApplicationContext context = null; + Collection exceptionReporters = new ArrayList(); + this.configureHeadlessProperty(); + SpringApplicationRunListeners listeners = this.getRunListeners(args); // 2. + listeners.starting(); // + + Collection exceptionReporters; + try { + ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // + ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); // + this.configureIgnoreBeanInfo(environment); // + Banner printedBanner = this.printBanner(environment); // + context = this.createApplicationContext(); // + exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); + this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);// + this.refreshContext(context); // + this.afterRefresh(context, applicationArguments); // + stopWatch.stop();// + if (this.logStartupInfo) { + (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); + } + + listeners.started(context); + this.callRunners(context, applicationArguments); + } catch (Throwable var10) { + this.handleRunFailure(context, var10, exceptionReporters, listeners); + throw new IllegalStateException(var10); + } + + try { + listeners.running(context); + return context; + } catch (Throwable var9) { + this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); + throw new IllegalStateException(var9); + } + } +``` + +- 创建计时器StopWatch +- 获取SpringApplicationRunListeners并启动 +- 创建ApplicationArguments +- 创建并初始化ConfigurableEnvironment +- 打印Banner +- 创建ConfigurableApplicationContext +- 准备ConfigurableApplicationContext +- 刷新ConfigurableApplicationContext,**这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看** +- 容器刷新后动作,啥都没做 +- 计时器停止计时 + +## refreshContext() + +**该源码中其实就是Spring源码的refresh()的源码** + +- **不过这里的refresh()是在`AbstractApplicationContext`抽象类上** + +- **其他就不提了,关注点在onrefresh()方法上,但是个空方法,毕竟是抽象类,去找其子类继承的它** +- **debug调试可以找到ServletWebServerApplicationContext** + +## ServletWebServerApplicationContext + +**先看个类图吧,很吊** + +![](http://media.dreamcat.ink//20200203211202.png) + +- `onRefresh()`->`createWebServer()`->`getWebServerFactory()`,此时已经加载了个web容器 +- 可以返回刚才的`createWebServer()`,然后看`factory.getWebServer` + +```java +public WebServer getWebServer(ServletContextInitializer... initializers) { + //tomcat这位大哥出现了 + Tomcat tomcat = new Tomcat(); + File baseDir = (this.baseDirectory != null ? this.baseDirectory + : createTempDir("tomcat")); + tomcat.setBaseDir(baseDir.getAbsolutePath()); + Connector connector = new Connector(this.protocol); + tomcat.getService().addConnector(connector); + customizeConnector(connector); + tomcat.setConnector(connector); + tomcat.getHost().setAutoDeploy(false); + configureEngine(tomcat.getEngine()); + for (Connector additionalConnector : this.additionalTomcatConnectors) { + tomcat.getService().addConnector(additionalConnector); + } + prepareContext(tomcat.getHost(), initializers); + return getTomcatWebServer(tomcat); + } +``` + +- 内置的Tomcat就出现了 +- **总结:run() 方法主要调用了spring容器启动方法扫描配置,加载bean到spring容器中;启动的内置Web容器** + +## @SpringBootApplication + +**主要是三个注解** + +- @SpringBootConfiguration:允许在上下文中注册额外的bean或导入其他配置类。 +- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制 +- @ComponentScan: 扫描常用的注解 + + diff --git "a/Java/spring-books/Spring\345\222\214SpringAOP\346\272\220\347\240\201\346\200\273\347\273\223.md" "b/Java/spring-books/Spring\345\222\214SpringAOP\346\272\220\347\240\201\346\200\273\347\273\223.md" new file mode 100644 index 00000000..ed5acff6 --- /dev/null +++ "b/Java/spring-books/Spring\345\222\214SpringAOP\346\272\220\347\240\201\346\200\273\347\273\223.md" @@ -0,0 +1,253 @@ +## 引言 + +> 使用了SpringBoot框架做项目,难道就不好奇它为什么那么方便吗?简化了很多配置,那么首先就先分析Spring底层的源码和原理 + + +## Spring源码分析 + +**Spring容器的创建,最重要的就是`refresh()`[创建刷新],在该方法内部,有着12大步骤流程。** + +**Spring版本:5.2.1.RELEASE** + +```java + public void refresh() throws BeansException, IllegalStateException { + synchronized(this.startupShutdownMonitor) { + this.prepareRefresh(); // 1. + ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 2. + this.prepareBeanFactory(beanFactory); // 3. + + try { + this.postProcessBeanFactory(beanFactory); // 4. + this.invokeBeanFactoryPostProcessors(beanFactory); // 5. + this.registerBeanPostProcessors(beanFactory); // 6. + this.initMessageSource(); // 7. + this.initApplicationEventMulticaster(); // 8. + this.onRefresh(); // 9. + this.registerListeners(); // 10. + this.finishBeanFactoryInitialization(beanFactory); // 11. + this.finishRefresh(); // 12. + } catch (BeansException var9) { + if (this.logger.isWarnEnabled()) { + this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); + } + + this.destroyBeans(); + this.cancelRefresh(var9); + throw var9; + } finally { + this.resetCommonCaches(); + } + } + } +``` + + + +### prepareRefresh() + +**刷新前的预处理如一些属性设置,环境,容器的一些早起事件等,主要执行的三个方法:** + +1. `initPropertySource()`初始化一些属性设置,子类自定义个性化的属性设置方法; +2. `getEnvironment().validateRequiredProperties()`检验属性的合法等 +3. `earlyApplicationEvents= new LinkedHashSet();`保存容器中的一些早起的事件; + +### obtainFreshBeanFactory() + +**获取BeanFactory,主要也是二个方法:** + +1. `refreshBeanFactory();`负责刷新创建BeanFactory + 1. 创建了一个默认的`this.beanFactory = new DefaultListableBeanFactory();` + 2. 设置id; +2. `getBeanFactory();`回刚才GenericApplicationContext创建的BeanFactory对象; + +### prepareBeanFactory(beanFactory) + +**BeanFactory的预准备工作(BeanFactory进行一些设置);** + +1. 设置BeanFactory的类加载器、支持表达式解析器... +2. 添加部分BeanPostProcessor【ApplicationContextAwareProcessor】 +3. 设置忽略的自动装配的接口EnvironmentAware、EmbeddedValueResolverAware、xxx; +4. 注册可以解析的自动装配;我们能直接在任何组件中自动注入:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext +5. 添加BeanPostProcessor【ApplicationListenerDetector】 +6. 添加编译时的AspectJ; +7. 给BeanFactory中注册一些能用的组件;environment【ConfigurableEnvironment】、systemProperties【Map】、systemEnvironment【Map + +### postProcessBeanFactory(beanFactory) + +**BeanFactory准备工作完成后进行的后置处理工作;** + +1. 子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置 + +### invokeBeanFactoryPostProcessors(beanFactory) + +**执行BeanFactoryPostProcessor的方法,BeanFactoryPostProcessor:BeanFactory的后置处理器。在BeanFactory标准初始化之后执行的;** + +**分别是两个接口:两个接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor** + +1. 先执行BeanDefinitionRegistryPostProcessor(注册bean的一堆定义信息) + 1. 获取所有的BeanDefinitionRegistryPostProcessor; + 2. 看先执行实现了PriorityOrdered优先级接口的BeanDefinitionRegistryPostProcessor、postProcessor.postProcessBeanDefinitionRegistry(registry) + 3. 在执行实现了Ordered顺序接口的BeanDefinitionRegistryPostProcessor;postProcessor.postProcessBeanDefinitionRegistry(registry) + 4. 最后执行没有实现任何优先级或者是顺序接口的BeanDefinitionRegistryPostProcessors;postProcessor.postProcessBeanDefinitionRegistry(registry) +2. 再执行BeanFactoryPostProcessor的方法 + 1. 获取所有的BeanFactoryPostProcessor + 2. 看先执行实现了PriorityOrdered优先级接口的BeanFactoryPostProcessor、 + postProcessor.postProcessBeanFactory() + 3. 在执行实现了Ordered顺序接口的BeanFactoryPostProcessor; + postProcessor.postProcessBeanFactory() + 4. 最后执行没有实现任何优先级或者是顺序接口的BeanFactoryPostProcessor; + postProcessor.postProcessBeanFactory() + +### registerBeanPostProcessors(beanFactory) + +**注册BeanPostProcessor(Bean的后置处理器)【 intercept bean creation】** + +**不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的:** + +- BeanPostProcessor +- DestructionAwareBeanPostProcessor +- InstantiationAwareBeanPostProcessor +- SmartInstantiationAwareBeanPostProcessor +- MergedBeanDefinitionPostProcessor【internalPostProcessors】 + +1. 获取所有的 BeanPostProcessor;后置处理器都默认可以通过PriorityOrdered、Ordered接口来执行优先级 +2. 先注册PriorityOrdered优先级接口的BeanPostProcessor;把每一个BeanPostProcessor;添加到BeanFactory中 +3. 再注册Ordered接口的 +4. 最后注册没有实现任何优先级接口的 +5. 最终注册MergedBeanDefinitionPostProcessor; +6. 注册一个ApplicationListenerDetector;来在Bean创建完成后检查是否是ApplicationListener,如果是applicationContext.addApplicationListener((ApplicationListener) bean); + +### initMessageSource() + +**初始化MessageSource组件(做国际化功能;消息绑定,消息解析);** + +1. 获取BeanFactory +2. 看容器中是否有id为messageSource的,类型是MessageSource的组件;MessageSource:取出国际化配置文件中的某个key的值;能按照区域信息获取; + 1. 如果有赋值给messageSource + 2. 如果没有自己创建一个DelegatingMessageSource; + +3. 把创建好的MessageSource注册在容器中,以后获取国际化配置文件的值的时候,可以自动注入MessageSource; + + ```java + beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource); + MessageSource.getMessage(String code, Object[] args, String defaultMessage, Locale locale); + ``` + +### initApplicationEventMulticaster() + +**初始化事件派发器;** + +1. 获取BeanFactory +2. 从BeanFactory中获取applicationEventMulticaster的ApplicationEventMulticaster; +3. 如果上一步没有配置;创建一个SimpleApplicationEventMulticaster +4. 将创建的ApplicationEventMulticaster添加到BeanFactory中,以后其他组件直接自动注入 + +### onRefresh() + +**留给子容器(子类)** + +1. 子类重写这个方法,在容器刷新的时候可以自定义逻辑; + +### registerListeners() + +**给容器中将所有项目里面的ApplicationListener注册进来;** + +1. 从容器中拿到所有的ApplicationListener +2. 将每个监听器添加到事件派发器中;`getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);` +3. 派发之前步骤产生的事件; + +### finishBeanFactoryInitialization(beanFactory) + +**初始化所有剩下的单实例bean;** + +1. 获取容器中的所有Bean,依次进行初始化和创建对象 +2. 获取Bean的定义信息;RootBeanDefinition +3. 判断Bean是非抽象的,是单实例的,是非懒加载; + 1. 判断是否是FactoryBean;是否是实现FactoryBean接口的Bean; + 2. 不是工厂Bean。利用getBean(beanName);创建对象 + 1. `getBean(beanName);`-->` ioc.getBean();` + 2. `doGetBean(name, null, null, false);` + 3. 先获取缓存中保存的单实例Bean。如果能获取到说明这个Bean之前被创建过(所有创建过的单实例Bean都会被缓存起来)`private final Map singletonObjects = new ConcurrentHashMap(256);` + 4. 缓存中获取不到,开始Bean的创建对象流程; + 5. 标记当前bean已经被创建 + 6. 获取Bean的定义信息; + 7. 【获取当前Bean依赖的其他Bean;如果有按照getBean()把依赖的Bean先创建出来;】 + 8. 启动单实例Bean的创建流程; + 1. `createBean(beanName, mbd, args);` + 2. `Object bean = resolveBeforeInstantiation(beanName, mbdToUse);`让BeanPostProcessor先拦截返回代理对象;【InstantiationAwareBeanPostProcessor】:提前执行;先触发:postProcessBeforeInstantiation();如果有返回值:触发postProcessAfterInitialization(); + 3. 如果前面的InstantiationAwareBeanPostProcessor没有返回代理对象;调用4) + 4. `Object beanInstance = doCreateBean(beanName, mbdToUse, args);`创建Bean + 1. 【创建Bean实例】;`createBeanInstance(beanName, mbd, args);`利用工厂方法或者对象的构造器创建出Bean实例; + 2. `applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);`调用`MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition(mbd, beanType, beanName);` + 3. 【Bean属性赋值】`populateBean(beanName, mbd, instanceWrapper);`赋值之前: + 1. 拿到InstantiationAwareBeanPostProcessor后置处理器;postProcessAfterInstantiation(); + 2. 拿到InstantiationAwareBeanPostProcessor后置处理器;postProcessPropertyValues(); + 3. 应用Bean属性的值;为属性利用setter方法等进行赋值;`applyPropertyValues(beanName, mbd, bw, pvs);` + 4. 【Bean初始化】`initializeBean(beanName, exposedObject, mbd);` + 1. 【执行Aware接口方法】`invokeAwareMethods(beanName, bean);`执行xxxAware接口的方法 BeanNameAware\BeanClassLoaderAware\BeanFactoryAware + 2. 【执行后置处理器初始化之前】`applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);` + 3. 【执行初始化方法】`invokeInitMethods(beanName, wrappedBean, mbd);` + 1. 是否是InitializingBean接口的实现;执行接口规定的初始化; + 2. 是否自定义初始化方法; + 4. 【执行后置处理器初始化之后】applyBeanPostProcessorsAfterInitialization-->BeanPostProcessor.postProcessAfterInitialization(); + 5. 注册Bean的销毁方法; + 5. 将创建的Bean添加到缓存中singletonObjects;ioc容器就是这些Map;很多的Map里面保存了单实例Bean,环境信息。。。。; +4. 所有Bean都利用getBean创建完成以后;检查所有的Bean是否是SmartInitializingSingleton接口的;如果是;就执行afterSingletonsInstantiated(); + +### finishRefresh() + +**完成BeanFactory的初始化创建工作;IOC容器就创建完成;** + +1. initLifecycleProcessor();初始化和生命周期有关的后置处理器;LifecycleProcessor + 1. 默认从容器中找是否有lifecycleProcessor的组件【LifecycleProcessor】;如果没有`new DefaultLifecycleProcessor();`加入到容器; + 2. 写一个LifecycleProcessor的实现类,可以在BeanFactory--> + void onRefresh(); + void onClose(); +2. getLifecycleProcessor().onRefresh();拿到前面定义的生命周期处理器(BeanFactory);回调onRefresh(); +3. `publishEvent(new ContextRefreshedEvent(this));`发布容器刷新完成事件; +4. `liveBeansView.registerApplicationContext(this);` + +### Spring源码总结 + +1. Spring容器在启动的时候,先会保存所有注册进来的Bean的定义信息; + 1. xml注册bean; + 2. 注解注册Bean;@Service、@Component、@Bean、xxx + 2. Spring容器会合适的时机创建这些Bean + 1. 用到这个bean的时候;利用getBean创建bean;创建好以后保存在容器中; + 2. 统一创建剩下所有的bean的时候;finishBeanFactoryInitialization(); + 3. 后置处理器;BeanPostProcessor + 1. 每一个bean创建完成,都会使用各种后置处理器进行处理;来增强bean的功能;比如 + 1. AutowiredAnnotationBeanPostProcessor:处理自动注入 + 2. AnnotationAwareAspectJAutoProxyCreator:来做AOP功能; + 3. xxx + 4. 事件驱动模型; + 1. ApplicationListener;事件监听; + 2. ApplicationEventMulticaster;事件派发: + +## SpringAOP源码分析 + +**实际上,Spring容器过程当中,如果开启了AOP功能,那么会创建一个后置器[AnnotationAwareAspectJAutoProxyCreator],看到UML图就明白了它的特点了。** + +![AnnotationAwareAspectJAutoProxyCreator](http://media.dreamcat.ink//20200201150935.png) + +**AOP具体流程,就不赘述了,毕竟其中有很多Spring容器创建的很多步骤,直接看总结即可包括了整个流程了** + +### 总结 + +1. @EnableAspectJAutoProxy 开启AOP功能 +2. @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator +3. AnnotationAwareAspectJAutoProxyCreator是一个后置处理器; +4. 容器的创建流程: + 1. registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象(**Spring源码**) + 2. finishBeanFactoryInitialization()初始化剩下的单实例bean(**Spring源码**) + 1. 创建业务逻辑组件和切面组件 + 2. AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 + 3. 组件创建完之后,判断组件是否需要增强;是->切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib); +5. 执行目标方法: + 1. 代理对象执行目标方法 + 2. CglibAopProxy.intercept(); + 1. 得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) + 2. 利用拦截器的链式机制,依次进入每一个拦截器进行执行; + 3. 效果: + 1. 正常执行:前置通知-》目标方法-》后置通知-》返回通知 + 2. 出现异常:前置通知-》目标方法-》后置通知-》异常通知 \ No newline at end of file diff --git "a/Java/spring-books/docs/1.xml\346\263\250\350\247\243bean\346\226\271\345\274\217.md" "b/Java/spring-books/docs/1.xml\346\263\250\350\247\243bean\346\226\271\345\274\217.md" new file mode 100644 index 00000000..54fe497d --- /dev/null +++ "b/Java/spring-books/docs/1.xml\346\263\250\350\247\243bean\346\226\271\345\274\217.md" @@ -0,0 +1,17 @@ +# 1.xml注解bean方式 + +```xml + + + + + + + + + + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/10.bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/Java/spring-books/docs/10.bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" new file mode 100644 index 00000000..97298ca5 --- /dev/null +++ "b/Java/spring-books/docs/10.bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -0,0 +1,78 @@ +# 10.bean的生命周期 + +```java +/** + * @program SpringBooks + * @description: bean的生命周期 + * @author: mf + * @create: 2020/01/28 23:23 + */ + +package org.example.config; + +import org.example.bean.Car; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + + +/** + * bean的生命周期: + * bean创建---初始化----销毁的过程 + * 容器管理bean的生命周期; + * 我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法 + * + * 构造(对象创建) + * 单实例:在容器启动的时候创建对象 + * 多实例:在每次获取的时候创建对象\ + * + * BeanPostProcessor.postProcessBeforeInitialization + * 初始化: + * 对象创建完成,并赋值好,调用初始化方法。。。 + * BeanPostProcessor.postProcessAfterInitialization + * 销毁: + * 单实例:容器关闭的时候 + * 多实例:容器不会管理这个bean;容器不会调用销毁方法; + * + * + * 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization, + * 一但返回null,跳出for循环,不会执行后面的BeanPostProcessor.postProcessorsBeforeInitialization + * + * BeanPostProcessor原理 + * populateBean(beanName, mbd, instanceWrapper);给bean进行属性赋值 + * initializeBean + * { + * applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + * invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化 + * applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + *} + * + * + * + * 1)、指定初始化和销毁方法; + * 通过@Bean指定init-method和destroy-method; + * 2)、通过让Bean实现InitializingBean(定义初始化逻辑), + * DisposableBean(定义销毁逻辑); + * 3)、可以使用JSR250; + * @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法 + * @PreDestroy:在容器销毁bean之前通知我们进行清理工作 + * 4)、BeanPostProcessor【interface】:bean的后置处理器; + * 在bean初始化前后进行一些处理工作; + * postProcessBeforeInitialization:在初始化之前工作 + * postProcessAfterInitialization:在初始化之后工作 + * + * Spring底层对 BeanPostProcessor 的使用; + * bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor; + * + */ +@Configuration +@ComponentScan("org.example.bean") +public class MainConfigOfLifeCycle { + + @Bean(initMethod = "init", destroyMethod = "destroy") + public Car car() { + return new Car(); + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/11.@Value\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/11.@Value\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..429ffe39 --- /dev/null +++ "b/Java/spring-books/docs/11.@Value\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,32 @@ +# @Value的使用 + +```java +/** + * @program SpringBooks + * @description: value的用法 + * @author: mf + * @create: 2020/01/29 21:04 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +//使用@Value赋值; +//1、基本数值 +//2、可以写SpEL; #{} +//3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) +@Configuration +@PropertySource(value = {"classpath:/person.properties"}) +public class MainConfigOfValue { + + @Bean + public Person person() { + return new Person(); + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/12.@PropertySource\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/12.@PropertySource\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..ef6a73a6 --- /dev/null +++ "b/Java/spring-books/docs/12.@PropertySource\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,32 @@ +# 12.@PropertySource的使用 + +```java +/** + * @program SpringBooks + * @description: value的用法 + * @author: mf + * @create: 2020/01/29 21:04 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +//使用@Value赋值; +//1、基本数值 +//2、可以写SpEL; #{} +//3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) +@Configuration +@PropertySource(value = {"classpath:/person.properties"}) +public class MainConfigOfValue { + + @Bean + public Person person() { + return new Person(); + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/13.@Autowired\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/13.@Autowired\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..1fc45ed4 --- /dev/null +++ "b/Java/spring-books/docs/13.@Autowired\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,71 @@ +# 13.@Autowired的使用 + +```java +/** + * @program SpringBooks + * @description: 自动装配 + * @author: mf + * @create: 2020/01/29 21:20 + */ + +package org.example.config; + +import org.example.service.BookService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * 自动装配; + * Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值; + * + * 1)、@Autowired:自动注入: + * 1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值 + * 2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找 + * applicationContext.getBean("bookDao") + * 3)、@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名 + * 4)、自动装配默认一定要将属性赋值好,没有就会报错; + * 可以使用@Autowired(required=false); + * 5)、@Primary:让Spring进行自动装配的时候,默认使用首选的bean; + * 也可以继续使用@Qualifier指定需要装配的bean的名字 + * BookService{ + * @Autowired + * BookDao bookDao; + * } + * + * 2)、Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解] + * @Resource: + * 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的; + * 没有能支持@Primary功能没有支持@Autowired(reqiured=false); + * @Inject: + * 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能; + * @Autowired:Spring定义的; @Resource、@Inject都是java规范 + * + * AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能; + * + * 3)、 @Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值 + * 1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配 + * 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取 + * 3)、放在参数位置: + * + * 4)、自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx); + * 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware; + * 把Spring底层一些组件注入到自定义的Bean中; + * xxxAware:功能使用xxxProcessor; + * ApplicationContextAware==》ApplicationContextAwareProcessor; + */ +@Configuration +@ComponentScan({"org.example.service", "org.example.controller"}) +public class MainConfigOfAutowired { + +// @Primary + @Bean("bookService2") + public BookService bookService() { + BookService bookService = new BookService(); + bookService.setLabel("2"); + return bookService; + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/14.@Qualifier\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/14.@Qualifier\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..aa91213b --- /dev/null +++ "b/Java/spring-books/docs/14.@Qualifier\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,71 @@ +# 14.@Qualifier的使用 + +```java +/** + * @program SpringBooks + * @description: 自动装配 + * @author: mf + * @create: 2020/01/29 21:20 + */ + +package org.example.config; + +import org.example.service.BookService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * 自动装配; + * Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值; + * + * 1)、@Autowired:自动注入: + * 1)、默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值 + * 2)、如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找 + * applicationContext.getBean("bookDao") + * 3)、@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名 + * 4)、自动装配默认一定要将属性赋值好,没有就会报错; + * 可以使用@Autowired(required=false); + * 5)、@Primary:让Spring进行自动装配的时候,默认使用首选的bean; + * 也可以继续使用@Qualifier指定需要装配的bean的名字 + * BookService{ + * @Autowired + * BookDao bookDao; + * } + * + * 2)、Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解] + * @Resource: + * 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的; + * 没有能支持@Primary功能没有支持@Autowired(reqiured=false); + * @Inject: + * 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能; + * @Autowired:Spring定义的; @Resource、@Inject都是java规范 + * + * AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能; + * + * 3)、 @Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值 + * 1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配 + * 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取 + * 3)、放在参数位置: + * + * 4)、自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx); + * 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware; + * 把Spring底层一些组件注入到自定义的Bean中; + * xxxAware:功能使用xxxProcessor; + * ApplicationContextAware==》ApplicationContextAwareProcessor; + */ +@Configuration +@ComponentScan({"org.example.service", "org.example.controller"}) +public class MainConfigOfAutowired { + +// @Primary + @Bean("bookService2") + public BookService bookService() { + BookService bookService = new BookService(); + bookService.setLabel("2"); + return bookService; + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/15.@Profile\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/15.@Profile\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..010988e1 --- /dev/null +++ "b/Java/spring-books/docs/15.@Profile\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,83 @@ +# 15.@Profile的使用 + +```java +/** + * @program SpringBooks + * @description: Profile + * @author: mf + * @create: 2020/01/29 21:45 + */ + +package org.example.config; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.PropertySource; + +import javax.sql.DataSource; + +/** + * Profile: + * Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能; + * + * 开发环境、测试环境、生产环境; + * 数据源:(/A)(/B)(/C); + * + * + * @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件 + * + * 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境 + * 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效 + * 3)、没有标注环境标识的bean在,任何环境下都是加载的; + */ +@Configuration +@PropertySource("classpath:/dbconfig.properties") +public class MainConfigOfProfile { + + @Value("${db.user}") + private String user; + + @Value("${db.driverClass}") + private String driverClass; + + + @Profile("test") + @Bean("testDataSource") + public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{ + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser(user); + dataSource.setPassword(pwd); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dshop?serverTimeZone=UTC"); + dataSource.setDriverClass(driverClass); + return dataSource; + } + + @Profile("dev") + @Bean("devDataSource") + public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{ + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser(user); + dataSource.setPassword(pwd); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dshop?serverTimeZone=UTC"); + dataSource.setDriverClass(driverClass); + return dataSource; + } + + @Profile("prod") + @Bean("prodDataSource") + public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{ + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser(user); + dataSource.setPassword(pwd); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dshop?serverTimeZone=UTC"); + dataSource.setDriverClass(driverClass); + return dataSource; + } + + +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/16.AOP\345\260\217\344\276\213\345\255\220.md" "b/Java/spring-books/docs/16.AOP\345\260\217\344\276\213\345\255\220.md" new file mode 100644 index 00000000..0c7d9a17 --- /dev/null +++ "b/Java/spring-books/docs/16.AOP\345\260\217\344\276\213\345\255\220.md" @@ -0,0 +1,210 @@ +# 16.AOP小例子 + +```java +/** + * @program SpringBooks + * @description: AOP配置文件 + * @author: mf + * @create: 2020/01/30 14:54 + */ + +package org.example.config; + +import org.example.aop.LogAspects; +import org.example.aop.MathCalculate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +/** + * AOP:【动态代理】 + * 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式; + * + * 1、导入aop模块;Spring AOP:(spring-aspects) + * 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx) + * 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行; + * 通知方法: + * 前置通知(@Before):logStart:在目标方法(div)运行之前运行 + * 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束) + * 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行 + * 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行 + * 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced()) + * 4、给切面类的目标方法标注何时何地运行(通知注解); + * 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中; + * 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect) + * [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】 + * 在Spring中很多的 @EnableXXX; + * + * 三步: + * 1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect) + * 2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式) + * 3)、开启基于注解的aop模式;@EnableAspectJAutoProxy + * + * AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?】 + * @EnableAspectJAutoProxy; + * 1、@EnableAspectJAutoProxy是什么? + * @Import(AspectJAutoProxyRegistrar.class):给容器中导入AspectJAutoProxyRegistrar + * 利用AspectJAutoProxyRegistrar自定义给容器中注册bean;BeanDefinetion + * internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator + * + * 给容器中注册一个AnnotationAwareAspectJAutoProxyCreator; + * + * 2、 AnnotationAwareAspectJAutoProxyCreator: + * AnnotationAwareAspectJAutoProxyCreator + * ->AspectJAwareAdvisorAutoProxyCreator + * ->AbstractAdvisorAutoProxyCreator + * ->AbstractAutoProxyCreator + * implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware + * 关注后置处理器(在bean初始化完成前后做事情)、自动装配BeanFactory + * + * AbstractAutoProxyCreator.setBeanFactory() + * AbstractAutoProxyCreator.有后置处理器的逻辑; + * + * AbstractAdvisorAutoProxyCreator.setBeanFactory()-》initBeanFactory() + * + * AnnotationAwareAspectJAutoProxyCreator.initBeanFactory() + * + * + * 流程: + * 1)、传入配置类,创建ioc容器 + * 2)、注册配置类,调用refresh()刷新容器; + * 3)、registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建; + * 1)、先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor + * 2)、给容器中加别的BeanPostProcessor + * 3)、优先注册实现了PriorityOrdered接口的BeanPostProcessor; + * 4)、再给容器中注册实现了Ordered接口的BeanPostProcessor; + * 5)、注册没实现优先级接口的BeanPostProcessor; + * 6)、注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中; + * 创建internalAutoProxyCreator的BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator】 + * 1)、创建Bean的实例 + * 2)、populateBean;给bean的各种属性赋值 + * 3)、initializeBean:初始化bean; + * 1)、invokeAwareMethods():处理Aware接口的方法回调 + * 2)、applyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization() + * 3)、invokeInitMethods();执行自定义的初始化方法 + * 4)、applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization(); + * 4)、BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功;--》aspectJAdvisorsBuilder + * 7)、把BeanPostProcessor注册到BeanFactory中; + * beanFactory.addBeanPostProcessor(postProcessor); + * =======以上是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程======== + * + * AnnotationAwareAspectJAutoProxyCreator => InstantiationAwareBeanPostProcessor + * 4)、finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;创建剩下的单实例bean + * 1)、遍历获取容器中所有的Bean,依次创建对象getBean(beanName); + * getBean->doGetBean()->getSingleton()-> + * 2)、创建bean + * 【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,InstantiationAwareBeanPostProcessor,会调用postProcessBeforeInstantiation()】 + * 1)、先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建; + * 只要创建好的Bean都会被缓存起来 + * 2)、createBean();创建bean; + * AnnotationAwareAspectJAutoProxyCreator 会在任何bean创建之前先尝试返回bean的实例 + * 【BeanPostProcessor是在Bean对象创建完成初始化前后调用的】 + * 【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的】 + * 1)、resolveBeforeInstantiation(beanName, mbdToUse);解析BeforeInstantiation + * 希望后置处理器在此能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续 + * 1)、后置处理器先尝试返回对象; + * bean = applyBeanPostProcessorsBeforeInstantiation(): + * 拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor; + * 就执行postProcessBeforeInstantiation + * if (bean != null) { +bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); +} + * + * 2)、doCreateBean(beanName, mbdToUse, args);真正的去创建一个bean实例;和3.6流程一样; + * 3)、 + * + * + * AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】 的作用: + * 1)、每一个bean创建之前,调用postProcessBeforeInstantiation(); + * 关心MathCalculator和LogAspect的创建 + * 1)、判断当前bean是否在advisedBeans中(保存了所有需要增强bean) + * 2)、判断当前bean是否是基础类型的Advice、Pointcut、Advisor、AopInfrastructureBean, + * 或者是否是切面(@Aspect) + * 3)、是否需要跳过 + * 1)、获取候选的增强器(切面里面的通知方法)【List candidateAdvisors】 + * 每一个封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor; + * 判断每一个增强器是否是 AspectJPointcutAdvisor 类型的;返回true + * 2)、永远返回false + * + * 2)、创建对象 + * postProcessAfterInitialization; + * return wrapIfNecessary(bean, beanName, cacheKey);//包装如果需要的情况下 + * 1)、获取当前bean的所有增强器(通知方法) Object[] specificInterceptors + * 1、找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的) + * 2、获取到能在bean使用的增强器。 + * 3、给增强器排序 + * 2)、保存当前bean在advisedBeans中; + * 3)、如果当前bean需要增强,创建当前bean的代理对象; + * 1)、获取所有增强器(通知方法) + * 2)、保存到proxyFactory + * 3)、创建代理对象:Spring自动决定 + * JdkDynamicAopProxy(config);jdk动态代理; + * ObjenesisCglibAopProxy(config);cglib的动态代理; + * 4)、给容器中返回当前组件使用cglib增强了的代理对象; + * 5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程; + * + * + * 3)、目标方法执行 ; + * 容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx); + * 1)、CglibAopProxy.intercept();拦截目标方法的执行 + * 2)、根据ProxyFactory对象获取将要执行的目标方法拦截器链; + * List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); + * 1)、List interceptorList保存所有拦截器 5 + * 一个默认的ExposeInvocationInterceptor 和 4个增强器; + * 2)、遍历所有的增强器,将其转为Interceptor; + * registry.getInterceptors(advisor); + * 3)、将增强器转为List; + * 如果是MethodInterceptor,直接加入到集合中 + * 如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor; + * 转换完成返回MethodInterceptor数组; + * + * 3)、如果没有拦截器链,直接执行目标方法; + * 拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制) + * 4)、如果有拦截器链,把需要执行的目标对象,目标方法, + * 拦截器链等信息传入创建一个 CglibMethodInvocation 对象, + * 并调用 Object retVal = mi.proceed(); + * 5)、拦截器链的触发过程; + * 1)、如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法; + * 2)、链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行; + * 拦截器链的机制,保证通知方法与目标方法的执行顺序; + * + * 总结: + * 1)、 @EnableAspectJAutoProxy 开启AOP功能 + * 2)、 @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator + * 3)、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器; + * 4)、容器的创建流程: + * 1)、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象 + * 2)、finishBeanFactoryInitialization()初始化剩下的单实例bean + * 1)、创建业务逻辑组件和切面组件 + * 2)、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 + * 3)、组件创建完之后,判断组件是否需要增强 + * 是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib); + * 5)、执行目标方法: + * 1)、代理对象执行目标方法 + * 2)、CglibAopProxy.intercept(); + * 1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) + * 2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行; + * 3)、效果: + * 正常执行:前置通知-》目标方法-》后置通知-》返回通知 + * 出现异常:前置通知-》目标方法-》后置通知-》异常通知 + * + * + * + */ +@EnableAspectJAutoProxy +@Configuration +public class MainConfigOfAOP { + + // 将业务逻辑加入容器中 + @Bean + public MathCalculate mathCalculate() { + return new MathCalculate(); + } + + // 将切面类加入容器中 + @Bean + public LogAspects logAspects() { + return new LogAspects(); + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/17.Spring\344\272\213\345\212\241.md" "b/Java/spring-books/docs/17.Spring\344\272\213\345\212\241.md" new file mode 100644 index 00000000..f9c7e23e --- /dev/null +++ "b/Java/spring-books/docs/17.Spring\344\272\213\345\212\241.md" @@ -0,0 +1,98 @@ +# 17.Spring事务 + +```java +/** + * @program SpringBooks + * @description: 事务配置 + * @author: mf + * @create: 2020/02/02 00:02 + */ + +package org.example.tx; + +import com.mchange.v2.c3p0.ComboPooledDataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +/** + * 声明式事务: + * + * 环境搭建: + * 1、导入相关依赖 + * 数据源、数据库驱动、Spring-jdbc模块 + * 2、配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据 + * 3、给方法上标注 @Transactional 表示当前方法是一个事务方法; + * 4、 @EnableTransactionManagement 开启基于注解的事务管理功能; + * @EnableXXX + * 5、配置事务管理器来控制事务; + * @Bean + * public PlatformTransactionManager transactionManager() + * + * + * 原理: + * 1)、@EnableTransactionManagement + * 利用TransactionManagementConfigurationSelector给容器中会导入组件 + * 导入两个组件 + * AutoProxyRegistrar + * ProxyTransactionManagementConfiguration + * 2)、AutoProxyRegistrar: + * 给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件; + * InfrastructureAdvisorAutoProxyCreator:? + * 利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用; + * + * 3)、ProxyTransactionManagementConfiguration 做了什么? + * 1、给容器中注册事务增强器; + * 1)、事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解 + * 2)、事务拦截器: + * TransactionInterceptor;保存了事务属性信息,事务管理器; + * 他是一个 MethodInterceptor; + * 在目标方法执行的时候; + * 执行拦截器链; + * 事务拦截器: + * 1)、先获取事务相关的属性 + * 2)、再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger + * 最终会从容器中按照类型获取一个PlatformTransactionManager; + * 3)、执行目标方法 + * 如果异常,获取到事务管理器,利用事务管理回滚操作; + * 如果正常,利用事务管理器,提交事务 + * + */ +@ComponentScan("org.example.tx") +@EnableTransactionManagement +@Configuration +public class MainConfigOfTx { + + // 配置数据源 + @Bean + public DataSource dataSource() throws Exception { + ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setUser("root"); + dataSource.setPassword("123"); + dataSource.setDriverClass("com.mysql.cj.jdbc.Driver"); + dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dshop?serverTimeZone=UTC"); + return dataSource; + } + + // jdbcTemplate + @Bean + public JdbcTemplate jdbcTemplate() throws Exception { + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource()); + return jdbcTemplate; + } + + // 事务管理器 + @Bean + public PlatformTransactionManager platformTransactionManager() throws Exception { + return new DataSourceTransactionManager(dataSource()); + } + +} + +``` \ No newline at end of file diff --git a/Java/spring-books/docs/18.BeanFactoryPostProcessor.md b/Java/spring-books/docs/18.BeanFactoryPostProcessor.md new file mode 100644 index 00000000..66ab02df --- /dev/null +++ b/Java/spring-books/docs/18.BeanFactoryPostProcessor.md @@ -0,0 +1,39 @@ +# 18.BeanFactoryPostProcessor + +```java +/** + * @program SpringBooks + * @description: MyBeanFactoryPostProcessor + * @author: mf + * @create: 2020/02/02 20:52 + */ + +package org.example.ext; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +@Component +public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + /** + * 重写该方法,目的研究一下它的作用,何时启动 + * @param configurableListableBeanFactory + * @throws BeansException + */ + public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { + System.out.println("MyBeanFactoryPostProcessor"); + int count = configurableListableBeanFactory.getBeanDefinitionCount(); + String[] names = configurableListableBeanFactory.getBeanDefinitionNames(); + System.out.println(count+" Bean"); + for (String name : names) { + System.out.println(name); + } + } +} + +``` \ No newline at end of file diff --git a/Java/spring-books/docs/19.BeanDefinitionRegistryPostProcessor.md b/Java/spring-books/docs/19.BeanDefinitionRegistryPostProcessor.md new file mode 100644 index 00000000..1ba0d259 --- /dev/null +++ b/Java/spring-books/docs/19.BeanDefinitionRegistryPostProcessor.md @@ -0,0 +1,32 @@ +# 19.BeanDefinitionRegistryPostProcessor + +```java +/** + * @program SpringBooks + * @description: MyBeanDefinitionRegistryPostProcessor + * @author: mf + * @create: 2020/02/02 23:18 + */ + +package org.example.ext; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.stereotype.Component; + +@Component +public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { + System.out.println("MyBeanDefinitionRegistryPostProcessor...bean的数量:"+beanDefinitionRegistry.getBeanDefinitionCount()); + } + + //BeanDefinitionRegistry Bean定义信息的保存中心,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实例; + public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { + System.out.println("postProcessBeanDefinitionRegistry...bean的数量:"+configurableListableBeanFactory.getBeanDefinitionCount()); + // 自己也可以在这里添加额外的bean + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/2.xml\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" "b/Java/spring-books/docs/2.xml\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" new file mode 100644 index 00000000..7720d8f2 --- /dev/null +++ "b/Java/spring-books/docs/2.xml\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" @@ -0,0 +1,17 @@ +# 2.xml注册包扫描方式 + +```xml + + + + + + + + + + +``` \ No newline at end of file diff --git a/Java/spring-books/docs/20.ApplicationListener.md b/Java/spring-books/docs/20.ApplicationListener.md new file mode 100644 index 00000000..ee0b3064 --- /dev/null +++ b/Java/spring-books/docs/20.ApplicationListener.md @@ -0,0 +1,25 @@ +# 20.ApplicationListener + +```java +/** + * @program SpringBooks + * @description: MyApplicationListener + * @author: mf + * @create: 2020/02/02 23:34 + */ + +package org.example.ext; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +public class MyApplicationListener implements ApplicationListener { + //当容器中发布此事件以后,方法触发 + public void onApplicationEvent(ApplicationEvent event) { + System.out.println("收到事件:" + event); + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/3.\346\263\250\350\247\243\346\263\250\345\206\214bean\346\226\271\345\274\217.md" "b/Java/spring-books/docs/3.\346\263\250\350\247\243\346\263\250\345\206\214bean\346\226\271\345\274\217.md" new file mode 100644 index 00000000..1f418f41 --- /dev/null +++ "b/Java/spring-books/docs/3.\346\263\250\350\247\243\346\263\250\345\206\214bean\346\226\271\345\274\217.md" @@ -0,0 +1,40 @@ +# 3.注解注册bean方式 + +```java +/** + * @program SpringBooks + * @description: 配置类 + * @author: mf + * @create: 2020/01/26 17:10 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 以前是配置文件,现在是配置类... 配置文件==配置类 + */ + +@Configuration // 告诉spring,这是一个配置文件 +@ComponentScan("org.example") +//@ComponentScan value:指定要扫描的包 +//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 +//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 +//FilterType.ANNOTATION:按照注解 +//FilterType.ASSIGNABLE_TYPE:按照给定的类型; +//FilterType.ASPECTJ:使用ASPECTJ表达式 +//FilterType.REGEX:使用正则指定 +//FilterType.CUSTOM:使用自定义规则 +public class MainConfig { + + // 给容器注册一个bean,类型为返回值的类型,id默认为方法名 + @Bean("person") + public Person person01() { + return new Person("class", 18); + } +} +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/4.\346\263\250\350\247\243\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" "b/Java/spring-books/docs/4.\346\263\250\350\247\243\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" new file mode 100644 index 00000000..22117709 --- /dev/null +++ "b/Java/spring-books/docs/4.\346\263\250\350\247\243\346\263\250\345\206\214\345\214\205\346\211\253\346\217\217\346\226\271\345\274\217.md" @@ -0,0 +1,41 @@ +# 4.注解注册包扫描方式 + +```java +/** + * @program SpringBooks + * @description: 配置类 + * @author: mf + * @create: 2020/01/26 17:10 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 以前是配置文件,现在是配置类... 配置文件==配置类 + */ + +@Configuration // 告诉spring,这是一个配置文件 +@ComponentScan("org.example") +//@ComponentScan value:指定要扫描的包 +//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 +//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 +//FilterType.ANNOTATION:按照注解 +//FilterType.ASSIGNABLE_TYPE:按照给定的类型; +//FilterType.ASPECTJ:使用ASPECTJ表达式 +//FilterType.REGEX:使用正则指定 +//FilterType.CUSTOM:使用自定义规则 +public class MainConfig { + + // 给容器注册一个bean,类型为返回值的类型,id默认为方法名 + @Bean("person") + public Person person01() { + return new Person("class", 18); + } +} + +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/5.@Filter\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/5.@Filter\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..73ff8179 --- /dev/null +++ "b/Java/spring-books/docs/5.@Filter\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,34 @@ +# 5.@Filter的使用 + +```java +/** + * @program SpringBooks + * @description: 过滤注解 + * @author: mf + * @create: 2020/01/28 22:19 + */ + +package org.example.config; + +import org.example.service.BookService; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.stereotype.Controller; + +@Configuration +@ComponentScan(value = "org.example", includeFilters = { + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BookService.class), + @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class) +}, useDefaultFilters = false) +//@ComponentScan value:指定要扫描的包 +//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 +//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 +//FilterType.ANNOTATION:按照注解 +//FilterType.ASSIGNABLE_TYPE:按照给定的类型; +//FilterType.ASPECTJ:使用ASPECTJ表达式 +//FilterType.REGEX:使用正则指定 +//FilterType.CUSTOM:使用自定义规则 +public class MainConfigOfFilter { +} +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/6.@Scope\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/6.@Scope\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..233e7cbc --- /dev/null +++ "b/Java/spring-books/docs/6.@Scope\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,50 @@ +# 6.@Scope的使用 + +```java +/** + * @program SpringBooks + * @description: Scope的使用 + * @author: mf + * @create: 2020/01/28 22:30 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; + +@Configuration +public class MainConfigOfScope { + + //默认是单实例的 + /** + * ConfigurableBeanFactory#SCOPE_PROTOTYPE + * @see ConfigurableBeanFactory#SCOPE_SINGLETON + * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request + * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion + * @return\ + * @Scope:调整作用域 + * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 + * 每次获取的时候才会调用方法创建对象; + * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 + * 以后每次获取就是直接从容器(map.get())中拿, + * request:同一次请求创建一个实例 + * session:同一个session创建一个实例 + * + * 懒加载: + * 单实例bean:默认在容器启动的时候创建对象; + * 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化; + * + */ +// @Scope("prototype") + @Lazy + @Bean + public Person person() { + System.out.println("给容器添加person"); + return new Person("Maifeng", 18); + } +} +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/7.@Lazy\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/7.@Lazy\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..41bf6d85 --- /dev/null +++ "b/Java/spring-books/docs/7.@Lazy\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,50 @@ +# 7.@Lazy的使用 + +```java +/** + * @program SpringBooks + * @description: Scope的使用 + * @author: mf + * @create: 2020/01/28 22:30 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; + +@Configuration +public class MainConfigOfScope { + + //默认是单实例的 + /** + * ConfigurableBeanFactory#SCOPE_PROTOTYPE + * @see ConfigurableBeanFactory#SCOPE_SINGLETON + * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request + * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion + * @return\ + * @Scope:调整作用域 + * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 + * 每次获取的时候才会调用方法创建对象; + * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 + * 以后每次获取就是直接从容器(map.get())中拿, + * request:同一次请求创建一个实例 + * session:同一个session创建一个实例 + * + * 懒加载: + * 单实例bean:默认在容器启动的时候创建对象; + * 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化; + * + */ +// @Scope("prototype") + @Lazy + @Bean + public Person person() { + System.out.println("给容器添加person"); + return new Person("Maifeng", 18); + } +} +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/8.@Conditional\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/8.@Conditional\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..37c8786c --- /dev/null +++ "b/Java/spring-books/docs/8.@Conditional\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,46 @@ +# 8.@Conditional的使用 + +```java +/** + * @program SpringBooks + * @description: Conditional + * @author: mf + * @create: 2020/01/28 23:03 + */ + +package org.example.config; + +import org.example.bean.Person; +import org.example.condition.ManCondition; +import org.example.condition.WomanCondition; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +/** + * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean + * + * 如果man,给容器中注册("Maifeng") + * 如果woman,给容器中注册("Liumeng") + */ +@Configuration +public class MainConfigOfConditional { + + @Bean("isMan") + public Person person() { + return new Person(); + } + + @Conditional(ManCondition.class) + @Bean("Man") + public Person person01() { + return new Person("Maifeng", 18); + } + + @Conditional(WomanCondition.class) + @Bean("Woman") + public Person person02() { + return new Person("Liumeng", 18); + } +} +``` \ No newline at end of file diff --git "a/Java/spring-books/docs/9.@Import\347\232\204\344\275\277\347\224\250.md" "b/Java/spring-books/docs/9.@Import\347\232\204\344\275\277\347\224\250.md" new file mode 100644 index 00000000..4c84632b --- /dev/null +++ "b/Java/spring-books/docs/9.@Import\347\232\204\344\275\277\347\224\250.md" @@ -0,0 +1,37 @@ +# 9.@Import的使用 + +```java +/** + * @program SpringBooks + * @description: Import的使用 + * @author: mf + * @create: 2020/01/28 23:12 + */ + +package org.example.config; + +import org.example.bean.Color; +import org.example.bean.Person; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({Person.class, Color.class}) +public class MainConfigOfImport { + + /** + * 给容器中注册组件; + * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类] + * 2)、@Bean[导入的第三方包里面的组件] + * 3)、@Import[快速给容器中导入一个组件] + * 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名 + * 2)、ImportSelector:返回需要导入的组件的全类名数组; + * 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中 + * 4)、使用Spring提供的 FactoryBean(工厂Bean); + * 1)、默认获取到的是工厂bean调用getObject创建的对象 + * 2)、要获取工厂Bean本身,我们需要给id前面加一个& + * &colorFactoryBean + */ +} + +``` \ No newline at end of file diff --git a/Java/spring-books/spring-aop2/src/Advice.java b/Java/spring-books/spring-aop2/src/Advice.java new file mode 100644 index 00000000..90654341 --- /dev/null +++ b/Java/spring-books/spring-aop2/src/Advice.java @@ -0,0 +1,7 @@ +import java.lang.reflect.InvocationHandler; + +/** + * 继承了 InvocationHandler 接口 + */ +public interface Advice extends InvocationHandler { +} diff --git a/Java/spring-books/spring-aop2/src/AfterAdvice.java b/Java/spring-books/spring-aop2/src/AfterAdvice.java new file mode 100644 index 00000000..5b776023 --- /dev/null +++ b/Java/spring-books/spring-aop2/src/AfterAdvice.java @@ -0,0 +1,29 @@ +import java.lang.reflect.Method; + +/** + * @program SpringBooks + * @description: 后置通知 + * @author: mf + * @create: 2020/02/04 20:23 + */ + +public class AfterAdvice implements Advice { + + private Object bean; + + private MethodInvocation methodInvocation; + + public AfterAdvice(Object bean, MethodInvocation methodInvocation) { + this.bean = bean; + this.methodInvocation = methodInvocation; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 业务逻辑调用 + Object invoke = method.invoke(bean, args); + // 后置通知调用 + methodInvocation.invoke(); + return invoke; + } +} diff --git a/Java/spring-books/spring-aop2/src/BeforeAdvice.java b/Java/spring-books/spring-aop2/src/BeforeAdvice.java new file mode 100644 index 00000000..b770a142 --- /dev/null +++ b/Java/spring-books/spring-aop2/src/BeforeAdvice.java @@ -0,0 +1,27 @@ +import java.lang.reflect.Method; + +/** + * @program SpringBooks + * @description: 前置通知 + * @author: mf + * @create: 2020/02/04 20:00 + */ + +public class BeforeAdvice implements Advice { + + private Object bean; // bean对象 + + private MethodInvocation methodInvocation; // 方法调用 + + public BeforeAdvice(Object bean, MethodInvocation methodInvocation) { + this.bean = bean; + this.methodInvocation = methodInvocation; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // 在目标方法前调用 + methodInvocation.invoke(); + return method.invoke(bean, args); + } +} diff --git a/Java/spring-books/spring-aop2/src/HelloService.java b/Java/spring-books/spring-aop2/src/HelloService.java new file mode 100644 index 00000000..02efd228 --- /dev/null +++ b/Java/spring-books/spring-aop2/src/HelloService.java @@ -0,0 +1,11 @@ +/** + * @program SpringBooks + * @description: HelloService + * @author: mf + * @create: 2020/02/04 20:05 + */ + +public interface HelloService { + + void sayHelloWorld(); +} diff --git a/Java/spring-books/spring-aop2/src/HelloServiceImpl.java b/Java/spring-books/spring-aop2/src/HelloServiceImpl.java new file mode 100644 index 00000000..a715ca8f --- /dev/null +++ b/Java/spring-books/spring-aop2/src/HelloServiceImpl.java @@ -0,0 +1,14 @@ +/** + * @program SpringBooks + * @description: HelloServiceImpl + * @author: mf + * @create: 2020/02/04 20:06 + */ + +public class HelloServiceImpl implements HelloService { + + @Override + public void sayHelloWorld() { + System.out.println("hello world..."); + } +} diff --git a/Java/spring-books/spring-aop2/src/MethodInvocation.java b/Java/spring-books/spring-aop2/src/MethodInvocation.java new file mode 100644 index 00000000..d8cc02da --- /dev/null +++ b/Java/spring-books/spring-aop2/src/MethodInvocation.java @@ -0,0 +1,11 @@ +/** + * @program SpringBooks + * @description: 实现类包含了切面逻辑 + * @author: mf + * @create: 2020/02/04 19:53 + */ + +public interface MethodInvocation { + + void invoke(); +} diff --git a/Java/spring-books/spring-aop2/src/SimpleAOP.java b/Java/spring-books/spring-aop2/src/SimpleAOP.java new file mode 100644 index 00000000..65279d18 --- /dev/null +++ b/Java/spring-books/spring-aop2/src/SimpleAOP.java @@ -0,0 +1,16 @@ +import java.lang.reflect.Proxy; + +/** + * @program SpringBooks + * @description: AOP的简单实现 + * @author: mf + * @create: 2020/02/04 20:02 + */ + +public class SimpleAOP { + + public static Object getProxy(Object bean, Advice advice) { + return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader() + , bean.getClass().getInterfaces(), advice); + } +} diff --git a/Java/spring-books/spring-aop2/src/SimpleAOPTest.java b/Java/spring-books/spring-aop2/src/SimpleAOPTest.java new file mode 100644 index 00000000..e65b64fe --- /dev/null +++ b/Java/spring-books/spring-aop2/src/SimpleAOPTest.java @@ -0,0 +1,29 @@ +/** + * @program SpringBooks + * @description: SimpleAOPTest + * @author: mf + * @create: 2020/02/04 20:06 + */ + +public class SimpleAOPTest { + public static void main(String[] args) { + // 1. 创建一个 MethodInvocation 实现类 切面逻辑类 + MethodInvocation logTask = () -> System.out.println("log task start"); + MethodInvocation logTaskEnd = () -> System.out.println("log task end"); + + // 业务逻辑类 + HelloServiceImpl helloService = new HelloServiceImpl(); + + // 2. 创建一个Advice 切入点 + BeforeAdvice beforeAdvice = new BeforeAdvice(helloService, logTask); + AfterAdvice afterAdvice = new AfterAdvice(helloService, logTaskEnd); + + // 3. 为目标对象生成代理 + HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloService, beforeAdvice); + HelloService helloServiceImplProxyAfter = (HelloService) SimpleAOP.getProxy(helloService, afterAdvice); + + helloServiceImplProxy.sayHelloWorld(); + helloServiceImplProxyAfter.sayHelloWorld(); + + } +} diff --git a/Java/spring-books/spring-ioc/src/Car.java b/Java/spring-books/spring-ioc/src/Car.java new file mode 100644 index 00000000..30dfe4f6 --- /dev/null +++ b/Java/spring-books/spring-ioc/src/Car.java @@ -0,0 +1,39 @@ +/** + * @program SpringBooks + * @description: Car + * @author: mf + * @create: 2020/02/04 01:58 + */ + +public class Car { + + private String name; + + private Wheel wheel; + + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Wheel getWheel() { + return wheel; + } + + public void setWheel(Wheel wheel) { + this.wheel = wheel; + } + + @Override + public String toString() { + return "Car{" + + "name='" + name + '\'' + + ", wheel=" + wheel + + '}'; + } +} diff --git "a/Interview/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" b/Java/spring-books/spring-ioc/src/SimpleIOC.java similarity index 57% rename from "Interview/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" rename to Java/spring-books/spring-ioc/src/SimpleIOC.java index 95863271..068ab59a 100644 --- "a/Interview/codes/spring/\346\211\213\345\206\231SpringIoc\345\256\271\345\231\250.md" +++ b/Java/spring-books/spring-ioc/src/SimpleIOC.java @@ -1,96 +1,25 @@ -# 手写SpringIoc代码 +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.FileInputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * @program SpringBooks + * @description: SimpleIOC + * @author: mf + * @create: 2020/02/04 01:36 + */ -## 举个例子 - -> 容器注册bean(Car和Wheel) - -### Wheel -```java -public class Wheel { - - private String brand; - - private String specification; - - public String getBrand() { - return brand; - } - - public void setBrand(String brand) { - this.brand = brand; - } - - public String getSpecification() { - return specification; - } - - public void setSpecification(String specification) { - this.specification = specification; - } - - @Override - public String toString() { - return "Wheel{" + - "brand='" + brand + '\'' + - ", specification='" + specification + '\'' + - '}'; - } -} -``` - -### Car -```java -public class Car { - - private String name; - - private Wheel wheel; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Wheel getWheel() { - return wheel; - } - - public void setWheel(Wheel wheel) { - this.wheel = wheel; - } - - @Override - public String toString() { - return "Car{" + - "name='" + name + '\'' + - ", wheel=" + wheel + - '}'; - } -} -``` - -### xml注册 -```xml - - - - - - - - - - - -``` - -### SimpleIOC -```java public class SimpleIOC { - //需要hashmap + private Map beanMap = new HashMap<>(); public SimpleIOC(String location) throws Exception { @@ -118,7 +47,7 @@ private void loadBean(String location) throws Exception { Element element = document.getDocumentElement(); // nodes NodeList nodes = element.getChildNodes(); - // 遍历标签获取bean节点信息 + // 便利标签 for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node instanceof Element) { @@ -128,13 +57,13 @@ private void loadBean(String location) throws Exception { // 加载beanClass Class beanClass = null; try { - beanClass = Class.forName(className);// 反射 + beanClass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); return; } // 创建bean - Object bean = beanClass.newInstance(); // new实例 + Object bean = beanClass.newInstance(); // 遍历标签 NodeList propertyNodes = ele.getElementsByTagName("property"); for (int j = 0; j < propertyNodes.getLength(); j++) { @@ -143,7 +72,7 @@ private void loadBean(String location) throws Exception { Element propertyElement = (Element) propertyNode; String name = propertyElement.getAttribute("name"); String value = propertyElement.getAttribute("value"); - // 利用反射将 bean 相关字段访问权限设为可访问 // 暴力访问 + // 利用反射将 bean 相关字段访问权限设为可访问 Field declaredField = bean.getClass().getDeclaredField(name); declaredField.setAccessible(true); @@ -170,19 +99,3 @@ private void registerBean(String id, Object bean) { beanMap.put(id, bean); } } -``` - -### SimpleIOCTest -```java -public class SimpleIOCTest { - public static void main(String[] args) throws Exception { - System.out.println(SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile()); - String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile(); - SimpleIOC simpleIOC = new SimpleIOC(location); - Wheel wheel = (Wheel) simpleIOC.getBean("wheel"); // 获取wheel bean - System.out.println(wheel); - Car car = (Car) simpleIOC.getBean("car"); // 获取 bean - System.out.println(car); - } -} -``` \ No newline at end of file diff --git a/Java/spring-books/spring-ioc/src/SimpleIOCTest.java b/Java/spring-books/spring-ioc/src/SimpleIOCTest.java new file mode 100644 index 00000000..56e9f96d --- /dev/null +++ b/Java/spring-books/spring-ioc/src/SimpleIOCTest.java @@ -0,0 +1,20 @@ +/** + * @program SpringBooks + * @description: SimpleIOCTest + * @author: mf + * @create: 2020/02/04 02:07 + */ + + +public class SimpleIOCTest { + + public static void main(String[] args) throws Exception { + System.out.println(SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile()); + String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile(); + SimpleIOC simpleIOC = new SimpleIOC(location); + Wheel wheel = (Wheel) simpleIOC.getBean("wheel"); + System.out.println(wheel); + Car car = (Car) simpleIOC.getBean("car"); + System.out.println(car); + } +} diff --git a/Java/spring-books/spring-ioc/src/Wheel.java b/Java/spring-books/spring-ioc/src/Wheel.java new file mode 100644 index 00000000..d8c23a41 --- /dev/null +++ b/Java/spring-books/spring-ioc/src/Wheel.java @@ -0,0 +1,39 @@ +/** + * @program SpringBooks + * @description: Wheel + * @author: mf + * @create: 2020/02/04 01:59 + */ + +public class Wheel { + + private String brand; + + private String specification; + + + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getSpecification() { + return specification; + } + + public void setSpecification(String specification) { + this.specification = specification; + } + + @Override + public String toString() { + return "Wheel{" + + "brand='" + brand + '\'' + + ", specification='" + specification + '\'' + + '}'; + } +} diff --git a/Java/spring-books/spring-ioc/src/ioc.xml b/Java/spring-books/spring-ioc/src/ioc.xml new file mode 100644 index 00000000..0d2d1a4b --- /dev/null +++ b/Java/spring-books/spring-ioc/src/ioc.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/JavaBooks.iml b/JavaBooks.iml deleted file mode 100644 index 8021953e..00000000 --- a/JavaBooks.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git "a/Jvm/JVM-\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/Jvm/JVM-\345\236\203\345\234\276\345\233\236\346\224\266.md" deleted file mode 100644 index 1f761d77..00000000 --- "a/Jvm/JVM-\345\236\203\345\234\276\345\233\236\346\224\266.md" +++ /dev/null @@ -1,312 +0,0 @@ -## 引言 - - - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... -> -> - 如何判断对象是否死亡(两种方法)。 -> - 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 -> - 如何判断一个常量是废弃常量 -> - 如何判断一个类是无用的类 -> - 垃圾收集有哪些算法,各自的特点? -> - HotSpot为什么要分为新生代和老年代? -> - 常见的垃圾回收器有哪些? -> - 介绍一下CMS,G1收集器。 -> - Minor Gc 和 Full GC有什么不同呢? - - - -## 揭开JVM内存分配与回收的神秘面纱 - -Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。 - -Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** - -**堆空间的基本结构:** - -
- -
- -上图所示的 eden 区、s0("From") 区、s1("To") 区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1("To"),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。经过这次GC后,Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。 - -![堆内存常见分配策略 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/堆内存.jpg) - -### 对象优先在eden区分配 - -目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 - -大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.下面我们来进行实际测试以下。 - -在测试之前我们先来看看 **Minor GC 和 Full GC 有什么不同呢?** - -- **新生代 GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。 -- **老年代 GC(Major GC/Full GC)**:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。 - -### 大对象直接进入老年代 - -大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。 - -**为什么要这样呢?** - -为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。 - -### 长期存活的对象将进入老年代 - -既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 - -如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 - -### 动态对象年龄判定 - -为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。 - -## 对象已经死亡? - -堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。 - -![参考-JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg) - -### 引用计数法 - -给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。 - -**这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。** 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。 - -```java -public class ReferenceCountingGc { - Object instance = null; - public static void main(String[] args) { - ReferenceCountingGc objA = new ReferenceCountingGc(); - ReferenceCountingGc objB = new ReferenceCountingGc(); - objA.instance = objB; - objB.instance = objA; - objA = null; - objB = null; - - } -} -``` - -### 可达性分析算法 - -这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。 - -![参考-JavaGuide-可达性分析算法 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg) - -- 哪些可以作为GC Roots的根 - - 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中应用的对象。 - - 方法区中的类静态属性引用的对象 - - 方法区中常量引用的对象 - - 本地方法栈中JNI(native方法)引用的对象 - -### 再谈引用 - -无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。 - -JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。 - -JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱) - -**1.强引用(StrongReference)** - -以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 - -**2.软引用(SoftReference)** - -如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 - -软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。 - -**3.弱引用(WeakReference)** - -如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 - -弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。 - -**4.虚引用(PhantomReference)** - -"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 - -**虚引用主要用来跟踪对象被垃圾回收的活动**。 - -**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 - -特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生**。 - -### 不可达的对象并非“非死不可” - -即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。 - -被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。 - -### 如何判断一个常量是废弃常量 - -运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢? - -假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。 - -### 如何判断一个类是无用的类 - -方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢? - -判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 **“无用的类”** : - -- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 -- 加载该类的 ClassLoader 已经被回收。 -- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 - -虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。 - -## 垃圾收集算法 - -![参考-JavaGuide-垃圾收集算法分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/垃圾收集算法.jpg) - -### 标记-清除算法 - -该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题: - -1. 效率问题 -2. 空间问题(标记清除后会产生大量不连续的碎片) - -公众号 - -### 标记-整理算法 - -根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 - -![参考-JavaGuide](https://www.pdai.tech/_images/pics/902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg) - -### 复制算法 - -为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。 - -公众号 - -### 分代收集算法 - -当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 - -**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** - -**延伸面试问题:** HotSpot 为什么要分为新生代和老年代? - -根据上面的对分代收集算法的介绍回答。 - -## 垃圾收集器 - -![参考-JavaGuide-垃圾收集器分类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/垃圾收集器.jpg) - -**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。** - -虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。 - -### Serial 收集器 - -Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 - -**新生代采用复制算法,老年代采用标记-整理算法。** - -![参考-JavaGuide Serial 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg) - -虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 - -但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。 - -### ParNew 收集器 - -**ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。** - - **新生代采用复制算法,老年代采用标记-整理算法。** - -![参考-JavaGuideParNew 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg) - -它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。 - -**并行和并发概念补充:** - -- **并行(Parallel)** :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 -- **并发(Concurrent)**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。 - -### Parallel Scavenge 收集器 - -Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 **那么它有什么特别之处呢?** - -**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。 - -**新生代采用复制算法,老年代采用标记-整理算法。** - -![参考-JavaGuideParallel Scavenge 收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg) - -### Serial Old 收集器 - -**Serial 收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。 - -### Parallel Old 收集器 - - **Parallel Scavenge 收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。 - -### CMS收集器 - -**CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。** - -**CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。** - -从名字中的**Mark Sweep**这两个词可以看出,CMS 收集器是一种 **“标记-清除”算法**实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤: - -- **初始标记:** 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ; -- **并发标记:** 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 -- **重新标记:** 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短 -- **并发清除:** 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。 - -![参考-JavaGuide-CMS 垃圾收集器 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg) - -从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:**并发收集、低停顿**。但是它有下面三个明显的缺点: - -- **对 CPU 资源敏感;** -- **无法处理浮动垃圾;** -- **它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。** - -### G1收集器 - -**G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.** - -被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点: - -- **并行与并发**:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。 -- **分代收集**:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。 -- **空间整合**:与 CMS 的“标记--清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。 -- **可预测的停顿**:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。 - -G1 收集器的运作大致分为以下几个步骤: - -- **初始标记** -- **并发标记** -- **最终标记** -- **筛选回收** - -**G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)**。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 GF 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。 - -## 如何选择垃圾选择器 - -- 单CPU或小内存,单机内存 - - -XX:+UseSerialGC - -- 多CPU,需要最大吞吐量,如后台计算型应用 - - -XX:+UseParallelGC -XX:+UseParallelOldGC - -- 多CPU,最求低停顿时间,需快速相应,如互联网应用 - - -XX:+ParNewGC -XX:+UseConcMarkSweepGC - -| 参数 | 新生代垃圾收集器 | 新生代算法 | 老年代垃圾收集器 | 老年代算法 | -| --------------------------------- | ------------------ | ------------------ | ------------------------------------------------------------ | ---------- | -| UseSerialGC | SerialGC | 复制 | SerialOldGC | 标整 | -| UseParNewGC | ParNew | 复制 | SerialOldGC | 标整 | -| UseParallelGC
UseParallelOldGC | Parallel[Scavenge] | 复制 | Parallel Old | 标整 | -| UseConcMarkSweepGC | ParNew | 复制 | CMS+Serial Old的收集器组合(Serial Old 作为CMS出错的后备收集器) | 标清 | -| UseG1GC | G1整体上采用标整 | 局部是通过复制算法 | | | \ No newline at end of file diff --git "a/Jvm/JVM-\345\257\271\350\261\241\345\210\233\345\273\272.md" "b/Jvm/JVM-\345\257\271\350\261\241\345\210\233\345\273\272.md" deleted file mode 100644 index 54846170..00000000 --- "a/Jvm/JVM-\345\257\271\350\261\241\345\210\233\345\273\272.md" +++ /dev/null @@ -1,288 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## 基本面试问题 - -- **介绍下 Java 内存区域(运行时数据区)** -- **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)** -- **对象的访问定位的两种方式(句柄和直接指针两种方式)** -- **String 类和常量池** -- **8 种基本类型的包装类和常量池** - -## 运行时数据区域 - -Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。 - -**JDK1.8之前** - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E5%9F%9F.png) -**JDK1.8之后** - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3Java%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BA%E5%9F%9FJDK1.8.png) - -**线程私有的:** - -- 程序计数器 -- 虚拟机栈 -- 本地方法栈 - -**线程共享的:** - -- 堆 -- 方法区 -- 直接内存 (非运行时数据区的一部分) - -### 程序计数器 - -程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。**字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。** - -另外,**为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。** - -**从上面的介绍中我们知道程序计数器主要有两个作用:** - -1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 - -**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。** - -### Java虚拟机栈 - -**与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。** - -**Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。) - -**局部变量表主要存放了编译器可知的各种数据类型**(boolean、byte、char、short、int、float、long、double)、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。 - -**Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。** - -- **StackOverFlowError:** 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。 -- **OutOfMemoryError:** 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。 - -Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。 - -Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。 - -Java 方法有两种返回方式: - -1. return语句 -2. 抛出异常。 - -### 本地方法栈 - -和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 - -本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 - -方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。 - -### 堆 - -Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。** - -Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3%E5%A0%86%E7%BB%93%E6%9E%84.png) - -上图所示的 eden 区、s0 区、s1 区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 - -### 方法区 - -方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 **Non-Heap(非堆)**,目的应该是与 Java 堆区分开来。 - -#### 方法区和永久代的关系 - -> 《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 **方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。** 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。 - -JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小 - -``` --XX:PermSize=N //方法区 (永久代) 初始大小 --XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen -``` - -相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。 - -JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。 - -下面是一些常用参数: - -``` --XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小) --XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小 -``` - -与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。 - -#### 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢? - -整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError。你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。 - -### 运行时常量池 - -运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用) - -既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。 - -**JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。** - -![参考-JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg) - -### 直接内存 - -**直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。** - -JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道(Channel)** 与**缓存区(Buffer)** 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。 - -本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 - -## HotSpot虚拟机对象探秘 - -### 对象的创建 - -大佬建议我们掌握每一步在做什么。 - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Java%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%BF%87%E7%A8%8B.png) - -1. 类加载检查,虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 - -2. 分配内存,在**类加载检查**通过后,接下来虚拟机将为新生对象**分配内存**。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。**分配方式**有 **“指针碰撞”** 和 **“空闲列表”** 两种,**选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定**。 - - - **内存分配的两种方式:(补充内容,需要掌握)** - - - 选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的 - - ![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F.png) - - - **内存分配并发问题(补充内容,需要掌握)** - - - 在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全: - - - **CAS+失败重试:** CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。**虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。** - - **TLAB:** 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配 - -3. 初始化零值,内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 - -4. 设置对象头,初始化零值完成之后,**虚拟机要对对象进行必要的设置**,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 **这些信息存放在对象头中。** 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 - -5. 执行init方法,在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,`` 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 `` 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 - -### 对象的内存布局 - -在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:**对象头**、**实例数据**和**对齐填充**。 - -**Hotspot 虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC 分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 - -**实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。 - -**对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。** 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 - -### 对象的访问定位 - -建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有**①使用句柄**和**②直接指针**两种: - -#### 使用句柄 - -如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D-%E4%BD%BF%E7%94%A8%E5%8F%A5%E6%9F%84.png) - -#### 直接指针 - -如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。 - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D-%E7%9B%B4%E6%8E%A5%E6%8C%87%E9%92%88.png) - -**这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。** - -## 重点补充内容 - -### String类和常量池 - -**String 对象的两种创建方式:** - -```java -String str1 = "abcd";//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd""; -String str2 = new String("abcd");//堆中创建一个新的对象 -String str3 = new String("abcd");//堆中创建一个新的对象 -System.out.println(str1==str2);//false -System.out.println(str2==str3);//false -``` - -这两种不同的创建方法是有差别的。 - -- 第一种方式是在常量池中拿对象; -- 第二种方式是直接在堆内存空间创建一个新的对象。 - -记住一点:**只要使用 new 方法,便需要创建新的对象。** - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3String-Pool-Java1-450x249.png) - -**String 类型的常量池比较特殊。它的主要使用方法有两种:** - -- 直接使用双引号声明出来的 String 对象会直接存储在常量池中。 -- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,JDK1.7之前(不包含1.7)的处理方式是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,JDK1.7以及之后的处理方式是在常量池中记录此字符串的引用,并返回该引用。 - -```java - String s1 = new String("计算机"); - String s2 = s1.intern(); - String s3 = "计算机"; - System.out.println(s2);//计算机 - System.out.println(s1 == s2);//false,因为一个是堆内存中的 String 对象一个是常量池中的 String 对象, - System.out.println(s3 == s2);//true,因为两个都是常量池中的 String 对象 -``` - -**字符串拼接:** - -```java - String str1 = "str"; - String str2 = "ing"; - - String str3 = "str" + "ing";//常量池中的对象 - String str4 = str1 + str2; //在堆上创建的新的对象 - String str5 = "string";//常量池中的对象 - System.out.println(str3 == str4);//false - System.out.println(str3 == str5);//true - System.out.println(str4 == str5);//false -``` - -![参考-JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8B%BC%E6%8E%A5.png) - -尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。 - -### 8种基本类型的包装类和常量池 - -- **Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;这 5 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。** -- **两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。** - -#### 应用场景 - -1. Integer i1=40;Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。 -2. Integer i1 = new Integer(40);这种情况下会创建新的对象。 - -```java - Integer i1 = 40; - Integer i2 = 40; - Integer i3 = 0; - Integer i4 = new Integer(40); - Integer i5 = new Integer(40); - Integer i6 = new Integer(0); - - System.out.println("i1=i2 " + (i1 == i2)); - System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); - System.out.println("i1=i4 " + (i1 == i4)); - System.out.println("i4=i5 " + (i4 == i5)); - System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); - System.out.println("40=i5+i6 " + (40 == i5 + i6)); -i1=i2 true -i1=i2+i3 true -i1=i4 false -i4=i5 false -i4=i5+i6 true -40=i5+i6 true -``` - - diff --git "a/Jvm/JVM-\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/Jvm/JVM-\347\261\273\345\212\240\350\275\275\345\231\250.md" deleted file mode 100644 index be6d2072..00000000 --- "a/Jvm/JVM-\347\261\273\345\212\240\350\275\275\345\231\250.md" +++ /dev/null @@ -1,63 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## 回顾一下类加载过程 - -类加载过程:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。 - -![参考-JavaGuide-类加载过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类加载过程.png) - -一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。 - -所有的类都由类加载器加载,加载的作用就是将 .class文件加载到内存。 - -## 类加载器总结 - -JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`: - -1. **BootstrapClassLoader(启动类加载器)** :最顶层的加载类,由C++实现,负责加载 `%JAVA_HOME%/lib`目录下的jar包和类或者或被 `-Xbootclasspath`参数指定的路径中的所有类。 -2. **ExtensionClassLoader(扩展类加载器)** :主要负责加载目录 `%JRE_HOME%/lib/ext` 目录下的jar包和类,或被 `java.ext.dirs` 系统变量所指定的路径下的jar包。 -3. **AppClassLoader(应用程序类加载器)** :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。 - -## 双亲委派模型 - -### 双亲委派模型介绍 - -每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。 - -![参考-JavaGuide-ClassLoader](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/classloader_WPS图片.png) - -```java -public class ClassLoaderDemo { - public static void main(String[] args) { - System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader()); - System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent()); - System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent()); - } -} -ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2 -The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@1b6d3586 -The GrandParent of ClassLodarDemo's ClassLoader is null -``` - -`AppClassLoader`的父类加载器为`ExtClassLoader` -`ExtClassLoader`的父类加载器为null,**null并不代表`ExtClassLoader`没有父类加载器,而是 `BootstrapClassLoader`** 。 - -其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。 - -### 双亲委派模型的好处 - -双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 `java.lang.Object` 类的话,那么程序运行的时候,系统就会出现多个不同的 `Object` 类。 - -### 如果我们不想用双亲委派模型怎么办? - -为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重写 `loadClass()` 即可。 - -## 自定义类加载器 - -除了 `BootstrapClassLoader` 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`。如果我们要自定义自己的类加载器,很明显需要继承 `ClassLoader`。 \ No newline at end of file diff --git "a/Jvm/JVM-\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" "b/Jvm/JVM-\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" deleted file mode 100644 index 2017aa6b..00000000 --- "a/Jvm/JVM-\347\261\273\345\212\240\350\275\275\350\277\207\347\250\213.md" +++ /dev/null @@ -1,62 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -# 类加载过程 - -Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? - -系统加载 Class 类型的文件主要三步:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。 - -![参考-JavaGuide-类加载过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类加载过程.png) - -## 加载 - -类加载过程的第一步,主要完成下面3件事情: - -1. 通过全类名获取定义此类的二进制字节流 -2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 -3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口 - -**一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。** - -类加载器、双亲委派模型也是非常重要的知识点,这部分内容会在后面的文章中单独介绍到。 - -加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。 - -## 验证 - -![参考-JavaGuide-验证阶段示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/验证阶段.png) - -## 准备 - -**准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: - -1. 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 -2. 这里所设置的初始值"通常情况"下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 fianl 关键字`public static final int value=111` ,那么准备阶段 value 的值就被复制为 111。 - -## 解析 - -解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。 - -符号引用就是一组符号来描述目标,可以是任何字面量。**直接引用**就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。 - -综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。 - -## 初始化 - -初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 ` ()`方法的过程。 - -对于`()` 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 `()` 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。 - -对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化: - -1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。 -2. 使用 `java.lang.reflect` 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。 -3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。 -4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。 -5. 当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。 \ No newline at end of file diff --git "a/Jvm/JVM-\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/Jvm/JVM-\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" deleted file mode 100644 index f2cc99c0..00000000 --- "a/Jvm/JVM-\347\261\273\346\226\207\344\273\266\347\273\223\346\236\204.md" +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: JVM-类文件结构 -author: DreamCat -id: 2 -date: 2019-11-26 17:02:11 -tags: JVM -categories: Java ---- -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## 概述 - -在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。 - -**可以说`.class`文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。** - -## Class文件结构总结 - -根据 Java 虚拟机规范,类文件由单个 ClassFile 结构组成: - -```java -ClassFile { - u4 magic; //Class 文件的标志 - u2 minor_version;//Class 的小版本号 - u2 major_version;//Class 的大版本号 - u2 constant_pool_count;//常量池的数量 - cp_info constant_pool[constant_pool_count-1];//常量池 - u2 access_flags;//Class 的访问标记 - u2 this_class;//当前类 - u2 super_class;//父类 - u2 interfaces_count;//接口 - u2 interfaces[interfaces_count];//一个类可以实现多个接口 - u2 fields_count;//Class 文件的字段属性 - field_info fields[fields_count];//一个类会可以有个字段 - u2 methods_count;//Class 文件的方法数量 - method_info methods[methods_count];//一个类可以有个多个方法 - u2 attributes_count;//此类的属性表中的属性数 - attribute_info attributes[attributes_count];//属性表集合 -} -``` - -下面详细介绍一下 Class 文件结构涉及到的一些组件。 - -**Class文件字节码结构组织示意图** (之前在网上保存的,非常不错,原出处不明): - -![参考-JavaGuide-类文件字节码结构组织示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类文件字节码结构组织示意图.png) - -### 魔数 - -` u4 magic; //Class 文件的标志` - -每个 Class 文件的头四个字节称为魔数(Magic Number),它的唯一作用是**确定这个文件是否为一个能被虚拟机接收的 Class 文件**。 - -### Class文件版本 - -```java - u2 minor_version;//Class 的小版本号 - u2 major_version;//Class 的大版本号 -``` - -紧接着魔数的四个字节存储的是 Class 文件的版本号:第五和第六是**次版本号**,第七和第八是**主版本号**。 - -高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。所以,我们在实际开发的时候要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。 - -### 常量池 - -```java - u2 constant_pool_count;//常量池的数量 - cp_info constant_pool[constant_pool_count-1];//常量池 -``` - -紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1(**常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”**)。 - -常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量: - -- 类和接口的全限定名 -- 字段的名称和描述符 -- 方法的名称和描述符 - -常量池中每一项常量都是一个表,这14种表有一个共同的特点:**开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.** - -### 访问标志 - -在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。 - -### 当前类索引,父类索引与接口索引集合 - -```java - u2 this_class;//当前类 - u2 super_class;//父类 - u2 interfaces_count;//接口 - u2 interfaces[interfaces_count];//一个雷可以实现多个接口 -``` - -**类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 `java.lang.Object` 之外,所有的 java 类都有父类,因此除了 `java.lang.Object` 外,所有 Java 类的父类索引都不为 0。** - -**接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按`implents`(如果这个类本身是接口的话则是`extends`) 后的接口顺序从左到右排列在接口索引集合中。** - -### 字段表集合 - -```java - u2 fields_count;//Class 文件的字段的个数 - field_info fields[fields_count];//一个类会可以有个字段 -``` - -字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量. - -### 方法表集合 - -```java - u2 methods_count;//Class 文件的方法的数量 - method_info methods[methods_count];//一个类可以有个多个方法 -``` - -methods_count 表示方法的数量,而 method_info 表示的方法表。 - -Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。 - -### 属性表集合 - -```java - u2 attributes_count;//此类的属性表中的属性数 - attribute_info attributes[attributes_count];//属性表集合 -``` - -在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。 \ No newline at end of file diff --git "a/Jvm/JVM\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Jvm/JVM\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index f2970f2a..00000000 --- "a/Jvm/JVM\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,29 +0,0 @@ -- [类文件结构]() - - [Class文件结构都有哪些?举点例子](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84.md#class%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E6%80%BB%E7%BB%93) - - [聊一聊对象头?](https://www.cnblogs.com/LemonFive/p/11246086.html) -- [类加载过程]() - - [加载](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.md#%E5%8A%A0%E8%BD%BD) - - [连接]() - - [验证](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.md#%E9%AA%8C%E8%AF%81) - - [准备](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.md#%E5%87%86%E5%A4%87) - - [解析](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.md#%E8%A7%A3%E6%9E%90) - - [初始化](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.md#%E5%88%9D%E5%A7%8B%E5%8C%96) - - [类加载器都有哪些?](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8.md#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%80%BB%E7%BB%93) - - [聊一聊双亲委派机制](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8.md#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B) - - [JVM里的符号引用如何存储?](https://www.zhihu.com/question/30300585) -- [JVM内存模型]() - - [聊一聊JVM内存模型](/Jvm/Java面经-内存模型.md) - - [堆的空间基本结构](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#%E6%8F%AD%E5%BC%80jvm%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E4%B8%8E%E5%9B%9E%E6%94%B6%E7%9A%84%E7%A5%9E%E7%A7%98%E9%9D%A2%E7%BA%B1) - - [请你谈谈对OOM的认识](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/Java%E9%9D%A2%E7%BB%8F-%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.md#%E8%AF%B7%E4%BD%A0%E8%B0%88%E8%B0%88%E5%AF%B9oom%E7%9A%84%E8%AE%A4%E8%AF%86) - - [如何判断对象已经死亡?都有哪些方法?](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#%E5%AF%B9%E8%B1%A1%E5%B7%B2%E7%BB%8F%E6%AD%BB%E4%BA%A1) - - [四大引用是什么?区别是什么?](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#%E5%86%8D%E8%B0%88%E5%BC%95%E7%94%A8) - - [聊一聊垃圾收集算法?](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95) - - [聊一聊垃圾收集器](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8) - - [如何选择垃圾回收器](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9%E5%9E%83%E5%9C%BE%E9%80%89%E6%8B%A9%E5%99%A8) -- [Java对象创建过程]() - - [谈一谈创建过程](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA.md#%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA) - - [内存布局是怎么样的?](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA.md#%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80) - - [对象的访问定位](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/JVM-%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA.md#%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D) -- [JVM参数]() - - [如何盘点查看JVM系统默认值](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/Java%E9%9D%A2%E7%BB%8F-JVM%E8%B0%83%E4%BC%98%E5%8F%82%E6%95%B0.md#%E5%A6%82%E4%BD%95%E7%9B%98%E7%82%B9%E6%9F%A5%E7%9C%8Bjvm%E7%B3%BB%E7%BB%9F%E9%BB%98%E8%AE%A4%E5%80%BC) - - [你平时工作用过的JVM常用基本配置参数有哪些](https://github.com/DreamCats/JavaBooks/blob/master/Jvm/Java%E9%9D%A2%E7%BB%8F-JVM%E8%B0%83%E4%BC%98%E5%8F%82%E6%95%B0.md#%E4%BD%A0%E5%B9%B3%E6%97%B6%E5%B7%A5%E4%BD%9C%E7%94%A8%E8%BF%87%E7%9A%84jvm%E5%B8%B8%E7%94%A8%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0%E6%9C%89%E5%93%AA%E4%BA%9B) diff --git "a/Jvm/Java\351\235\242\347\273\217-JVM\350\260\203\344\274\230\345\217\202\346\225\260.md" "b/Jvm/Java\351\235\242\347\273\217-JVM\350\260\203\344\274\230\345\217\202\346\225\260.md" deleted file mode 100644 index 172f21e1..00000000 --- "a/Jvm/Java\351\235\242\347\273\217-JVM\350\260\203\344\274\230\345\217\202\346\225\260.md" +++ /dev/null @@ -1,210 +0,0 @@ -## 引言 - -> **JVM - 参数调优** - -## 如何盘点查看JVM系统默认值 - -**如何查看运行中程序的JVM信息** - -- jps查看进程信息 -- jinfo -flag 配置项 晋城号 -- jinfo -flags 进程号(查看所有配置) - -#### JVM参数类型 - -1. 标配参 - 1. `-version -help` - 2. 各个版本之间稳定,很少有很大的变化 -2. x参数 - 1. `-Xint -Xcomp -Xmixed` - 2. -Xint:解释执行 - 3. -Xcomp:第一次使用就编译成本地代码 - 4. -Xmixed:混合模式 -3. **XX参数(重要)** - 1. Boolean类型 - 1. 公式:`-XX+或者-某个属性`---> +表示开启,-表示关闭 - 2. 比如:**是否打印GC收集细节 -XX:+PrintGCDetails -XX:-PrintGCDetails** - 3. 比如:**是否使用串行垃圾回收器:-XX:-UserSerialGC** - 2. KV设值类型 - 1. 公式:`-XX:key=value` - 2. 比如:`-XX:MetaspaceSize=128m` `-XX:MaxTenuringThreshold=15` `-Xms----> -XX:InitialHeapSize` `-Xmx----> -XX:MaxHeapSize` - -#### 查看参数 - -- `-XX:+PrintFlagsInitial`:查看初始默认,eg:`java -XX:+PrintFlagsInitial -version` -- `-XX:+PrintFlagsFinal`:查看修改后的 `:=`说明是修改过的 -- `-XX:+PrintCommandLineFlags`:查看使用的垃圾回收器 - -## JVM参数 - -- -Xms - -堆最小值 - -- -Xmx - -堆最大堆。-Xms与-Xmx 的单位默认字节都是以k、m做单位的。 - -通常这两个配置参数相等,避免每次空间不足,动态扩容带来的影响。 - - - -- -Xmn - -新生代大小 - -- -Xss - -每个线程池的堆栈大小。在jdk5以上的版本,每个线程堆栈大小为1m,jdk5以前的版本是每个线程池大小为256k。一般在相同物理内存下,如果减少-xss值会产生更大的线程数,但不同的操作系统对进程内线程数是有限制的,是不能无限生成。 - -- -XX:NewRatio - -设置新生代与老年代比值,-XX:NewRatio=4 表示新生代与老年代所占比例为1:4 ,新生代占比整个堆的五分之一。如果设置了-Xmn的情况下,该参数是不需要在设置的。 - -- -XX:PermSize - -设置持久代初始值,默认是物理内存的六十四分之一 - -- -XX:MaxPermSize - -设置持久代最大值,默认是物理内存的四分之一 - -- -XX:MaxTenuringThreshold - -新生代中对象存活次数,默认15。(若对象在eden区,经历一次MinorGC后还活着,则被移动到Survior区,年龄加1。以后,对象每次经历MinorGC,年龄都加1。达到阀值,则移入老年代) - -- -XX:SurvivorRatio - -Eden区与Subrvivor区大小的比值,如果设置为8,两个Subrvivor区与一个Eden区的比值为2:8,一个Survivor区占整个新生代的十分之一 - -- -XX:+UseFastAccessorMethods - -原始类型快速优化 - -- -XX:+AggressiveOpts - -编译速度加快 - -- -XX:PretenureSizeThreshold - -对象超过多大值时直接在老年代中分配 - -```text -说明: -整个堆大小的计算公式:JVM 堆大小 = 年轻代大小+年老代大小+持久代大小。 -增大新生代大小就会减少对应的年老代大小,设置-Xmn值对系统性能影响较大,所以如果设置新生代大小的调整,则需要严格的测试调整。而新生代是用来存放新创建的对象,大小是随着堆大小增大和减少而有相应的变化,默认值是保持堆大小的十五分之一,-Xmn参数就是设置新生代的大小,也可以通过-XX:NewRatio来设置新生代与年老代的比例,java 官方推荐配置为3:8。 - -新生代的特点就是内存中的对象更新速度快,在短时间内容易产生大量的无用对象,如果在这个参数时就需要考虑垃圾回收器设置参数也需要调整。推荐使用:复制清除算法和并行收集器进行垃圾回收,而新生代的垃圾回收叫做初级回收。 - -StackOverflowError和OutOfMemoryException。当线程中的请求的栈的深度大于最大可用深度,就会抛出前者;若内存空间不够,无法创建新的线程,则会抛出后者。栈的大小直接决定了函数的调用最大深度,栈越大,函数嵌套可调用次数就越多。 -``` - -## 经验 - -1. Xmn用于设置新生代的大小。过小会增加Minor GC频率,过大会减小老年代的大小。一般设为整个堆空间的1/4或1/3. -2. XX:SurvivorRatio用于设置新生代中survivor空间(from/to)和eden空间的大小比例; XX:TargetSurvivorRatio表示,当经历Minor GC后,survivor空间占有量(百分比)超过它的时候,就会压缩进入老年代(当然,如果survivor空间不够,则直接进入老年代)。默认值为50%。 -3. 为了性能考虑,一开始尽量将新生代对象留在新生代,避免新生的大对象直接进入老年代。因为新生对象大部分都是短期的,这就造成了老年代的内存浪费,并且回收代价也高(Full GC发生在老年代和方法区Perm). -4. 当Xms=Xmx,可以使得堆相对稳定,避免不停震荡 -5. 一般来说,MaxPermSize设为64MB可以满足绝大多数的应用了。若依然出现方法区溢出,则可以设为128MB。若128MB还不能满足需求,那么就应该考虑程序优化了,减少**动态类**的产生。 - -## 垃圾回收 - -### 垃圾回收算法 - -- 引用计数法:会有循环引用的问题,古老的方法; -- Mark-Sweep:标记清除。根可达判断,最大的问题是空间碎片(清除垃圾之后剩下不连续的内存空间); -- Copying:复制算法。对于短命对象来说有用,否则需要复制大量的对象,效率低。**如Java的新生代堆空间中就是使用了它(survivor空间的from和to区);** -- Mark-Compact:标记整理。对于老年对象来说有用,无需复制,不会产生内存碎片 - -### GC考虑的指标 - -- 吞吐量:应用耗时和实际耗时的比值; -- 停顿时间:垃圾回收的时候,由于Stop the World,应用程序的所有线程会挂起,造成应用停顿。 - -```text -吞吐量和停顿时间是互斥的。 -对于后端服务(比如后台计算任务),吞吐量优先考虑(并行垃圾回收); -对于前端应用,RT响应时间优先考虑,减少垃圾收集时的停顿时间,适用场景是Web系统(并发垃圾回收) -``` - -### 回收器的JVM参数 - -- -XX:+UseSerialGC - -串行垃圾回收,现在基本很少使用。 - -- -XX:+UseParNewGC - -新生代使用并行,老年代使用串行; - -- -XX:+UseConcMarkSweepGC - -新生代使用并行,老年代使用CMS(一般都是使用这种方式),CMS是Concurrent Mark Sweep的缩写,并发标记清除,一看就是老年代的算法,所以,它可以作为老年代的垃圾回收器。CMS不是独占式的,它关注停顿时间 - -- -XX:ParallelGCThreads - -指定并行的垃圾回收线程的数量,最好等于CPU数量 - -- -XX:+DisableExplicitGC - -禁用System.gc(),因为它会触发Full GC,这是很浪费性能的,JVM会在需要GC的时候自己触发GC。 - -- -XX:CMSFullGCsBeforeCompaction - -在多少次GC后进行内存压缩,这个是因为并行收集器不对内存空间进行压缩的,所以运行一段时间后会产生很多碎片,使得运行效率降低。 - -- -XX:+CMSParallelRemarkEnabled - -降低标记停顿 - -- -XX:+UseCMSCompactAtFullCollection - -在每一次Full GC时对老年代区域碎片整理,因为CMS是不会移动内存的,因此会非常容易出现碎片导致内存不够用的 - -- -XX:+UseCmsInitiatingOccupancyOnly - -使用手动触发或者自定义触发cms 收集,同时也会禁止hostspot 自行触发CMS GC - -- -XX:CMSInitiatingOccupancyFraction - -使用CMS作为垃圾回收,使用70%后开始CMS收集 - -- -XX:CMSInitiatingPermOccupancyFraction - -设置perm gen使用达到多少%比时触发垃圾回收,默认是92% - -- -XX:+CMSIncrementalMode - -设置为增量模式 - -- -XX:+CmsClassUnloadingEnabled - -CMS是不会默认对永久代进行垃圾回收的,设置此参数则是开启 - -- -XX:+PrintGCDetails - -开启详细GC日志模式,日志的格式是和所使用的算法有关 - -- -XX:+PrintGCDateStamps - -将时间和日期也加入到GC日志中 - -## 你平时工作用过的JVM常用基本配置参数有哪些 - -`-Xms` `-Xmx` `-Xmn` - -```shell --Xms128m -Xmx4096m -Xss1024K -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC -``` - -- -Xms:初始大小内存,默认为物理内存1/64,等价于-XX:InitialHeapSize -- -Xmx:最大分配内存,默认物理内存1/4,等价于-XX:MaxHeapSize -- -Xss:设置单个线程栈的大小,默认542K~1024K ,等价于-XX:ThreadStackSize -- -Xmn:设置年轻代的大小 -- -XX:MetaspaceSize:设置元空间大小 -- -XX:+PrintGCDetails:输出详细GC收集日志信息,如[名称:GC前内存占用->GC后内存占用(该区内存总大小)] -- -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例,默认-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1 -- -XX:NewRatio:设置年轻代与老年代在堆结构的占比,如:默认-XX:NewRatio=2 新生代在1,老年代2,年轻代占整个堆的1/3,NewRatio值几句诗设置老年代的占比,剩下的1给新生代 -- -XX:MaxTenuringThreshold:设置垃圾的最大年龄,默认-XX:MaxTenuringThreshold=15 -- -XX:+UseSerialGC:串行垃圾回收器 -- -XX:+UseParallelGC:并行垃圾回收器 \ No newline at end of file diff --git "a/Jvm/Java\351\235\242\347\273\217-\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/Jvm/Java\351\235\242\347\273\217-\345\206\205\345\255\230\346\250\241\345\236\213.md" deleted file mode 100644 index 3c59a0d2..00000000 --- "a/Jvm/Java\351\235\242\347\273\217-\345\206\205\345\255\230\346\250\241\345\236\213.md" +++ /dev/null @@ -1,86 +0,0 @@ -## 引言 - -**JVM - 内存模型** - - - -![](https://www.pdai.tech/_images/pics/c9ad2bf4-5580-4018-bce4-1b9a71804d9c.png) - -## 程序计数器 - -记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。 - -## Java虚拟机栈 - -每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息,从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 - -![](https://www.pdai.tech/_images/pics/926c7438-c5e1-4b94-840a-dcb24ff1dafe.png) - -可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:java -Xss512M HackTheJava - -该区域可能抛出以下异常: - -- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常; -- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。 - -## 本地方法栈 - -本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。 - -本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 - -## 堆 - -所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。 - -现代的垃圾收集器基本都是采用分代收集算法,针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块: - -- 新生代(Young Generation) -- 老年代(Old Generation) - -新生代可以继续划分成以下三个空间: - -- Eden(伊甸园) -- From Survivor(幸存者) -- To Survivor - -堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。 - -可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。java -Xms1M -Xmx2M HackTheJava - -## 方法区 - -用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 - -和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。 - -对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。 - -JDK 1.7 之前,HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是从 JDK 1.7 开始,已经把原本放在永久代的字符串常量池移到 Native Method 中。 - -## 运行时常量池 - -运行时常量池是方法区的一部分。 - -Class 文件中的常量池(编译器生成的各种字面量和符号引用)会在类加载后被放入这个区域。 - -除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。 - -## 直接内存 - -在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。 - -这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 - -## 请你谈谈对OOM的认识 - -- `java.lang.StackOverflowError`:栈空间溢出 ,递归调用卡死 -- `java.lang.OutOfMemoryError:Java heap space`:堆内存溢出 , 对象过大 -- `java.lang.OutOfMemoryError:GC overhead limit exceeded`:GC回收时间过长 -- `java.lang.OutOfMemoryError:Direct buffer memory`执行内存挂了,比如:NIO -- `java.lang.OutOfMemoryError:unable to create new native thread` - - 应用创建了太多线程,一个应用进程创建了多个线程,超过系统承载极限 - - 你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024,超过这个数量,就会报错 - - 解决办法:降低应用程序创建线程的数量,分析应用给是否针对需要这么多线程,如果不是,减到最低修改linux服务器配置 - -- `java.lang.OutOfMemoryError:Metaspace`:元空间主要存放了虚拟机加载的类的信息、常量池、静态变量、即时编译后的代码 \ No newline at end of file diff --git "a/Jvm/Java\351\235\242\347\273\217-\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md" "b/Jvm/Java\351\235\242\347\273\217-\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md" deleted file mode 100644 index 60c7880e..00000000 --- "a/Jvm/Java\351\235\242\347\273\217-\347\261\273\345\212\240\350\275\275\346\234\272\345\210\266.md" +++ /dev/null @@ -1,184 +0,0 @@ -## 引言 - -**JVM - 类加载机制** - -## 类的生命周期 - -其中类加载的过程包括了`加载`、`验证`、`准备`、`解析`、`初始化`五个阶段。在这五个阶段中,`加载`、`验证`、`准备`和`初始化`这四个阶段发生的顺序是确定的,*而`解析`阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)*。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。 - - - -![](https://www.pdai.tech/_images/jvm/java_jvm_classload_2.png) - -## 类的加载:查找并加载类的二进制数据 - -加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: - -- 通过一个类的全限定名来获取其定义的二进制字节流。 -- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 -- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。 - -![](https://www.pdai.tech/_images/jvm/java_jvm_classload_1.png)相对于类加载的其他阶段而言,*加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段*,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。 - -加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个`java.lang.Class`类的对象,这样便可以通过该对象访问方法区中的这些数据。 - -类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。 - -加载.class文件的方式 - -- 从本地系统中直接加载 -- 通过网络下载.class文件 -- 从zip,jar等归档文件中加载.class文件 -- 从专有数据库中提取.class文件 -- 将Java源文件动态编译为你.class文件 - -### 验证:确保被加载的类的正确性 - -验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作: - -- `文件格式验证`:验证字节流是否符合Class文件格式的规范;例如:是否以`0xCAFEBABE`开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。 -- `元数据验证`:对字节码描述的信息进行语义分析(注意:对比`javac`编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了`java.lang.Object`之外。 -- `字节码验证`:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。 -- `符号引用验证`:确保解析动作能正确执行。 - -验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,*如果所引用的类经过反复验证,那么可以考虑采用`-Xverifynone`参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。* - -### 准备:为类的静态变量分配内存,并将其初始化为默认值 - -准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,**这些内存都将在方法区中分配**。对于该阶段有以下几点需要注意: - -- 这时候进行内存分配的仅包括类变量(`static`),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。 -- 这里所设置的初始值通常情况下是数据类型默认的零值(如`0`、`0L`、`null`、`false`等),而不是被在Java代码中被显式地赋予的值。 - -假设一个类变量的定义为:`public static int value = 3`;那么变量value在准备阶段过后的初始值为`0`,而不是`3`,因为这时候尚未开始执行任何Java方法,而把value赋值为3的`put static`指令是在程序编译后,存放于类构造器`()`方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。 - -- 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。 -- 对于同时被`static`和`final`修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。 -- 对于引用数据类型`reference`来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即`null`。 -- 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。 -- 如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。假设上面的类变量value被定义为:`public static final int value = 3;`编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3。我们可以理解为`static final`常量在编译期就将其结果放入了调用它的类的常量池中 - -### 解析:把类中的符号引用转换为直接引用 - -解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对`类`或`接口`、`字段`、`类方法`、`接口方法`、`方法类型`、`方法句柄`和`调用点`限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。 - -`直接引用`就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。 - -### 初始化 - -初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式: - -- 声明类变量是指定初始值 -- 使用静态代码块为类变量指定初始值 - -#### JVM初始化步骤 - -- 假如这个类还没有被加载和连接,则程序先加载并连接该类 -- 假如该类的直接父类还没有被初始化,则先初始化其直接父类 -- 假如类中有初始化语句,则系统依次执行这些初始化语句 - -#### 类初始化时机 - -只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种: - -- 创建类的实例,也就是new的方式 -- 访问某个类或接口的静态变量,或者对该静态变量赋值 -- 调用类的静态方法 -- 反射 -- 初始化某个类的子类,则其父类也会被初始化 -- Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类 - -### 使用 - -类访问方法区内的数据结构的接口, 对象是Heap区的数据。 - -### 卸载 - -**Java虚拟机将结束生命周期的几种情况** - -- 执行了System.exit()方法 -- 程序正常执行结束 -- 程序在执行过程中遇到了异常或错误而异常终止 -- 由于操作系统出现错误而导致Java虚拟机进程终止 - -## 类加载器,JVM类加载机制 - -### 类加载器的层次 - -![](https://www.pdai.tech/_images/jvm/java_jvm_classload_3.png) - -这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。 - -站在Java虚拟机的角度来讲,只存在两种不同的类加载器:启动类加载器:它使用C++实现(这里仅限于`Hotspot`,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分;所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类`java.lang.ClassLoader`,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。 - -### 站在Java开发人员,类加载器可以大致划分为以下三类 - -`启动类加载器`:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。 - -`扩展类加载器`:Extension ClassLoader,该加载器由`sun.misc.Launcher$ExtClassLoader`实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。 - -`应用程序类加载器`:Application ClassLoader,该类加载器由`sun.misc.Launcher$AppClassLoader`来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 - -### 类的加载 - -- 命令行启动应用时候由JVM初始化加载 -- 通过Class.forName()方法动态加载 -- 通过ClassLoader.loadClass()方法动态加载 - -### JVM类加载机制 - -- `全盘负责`,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入 - -- `父类委托`,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类 - -- `缓存机制`,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效 - -- `双亲委派机制`, 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 - - - 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。 - - 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。 - - 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载; - - 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。 - - ```java - public Class loadClass(String name)throws ClassNotFoundException { - return loadClass(name, false); - } - protected synchronized Class loadClass(String name, boolean resolve)throws ClassNotFoundException { - // 首先判断该类型是否已经被加载 - Class c = findLoadedClass(name); - if (c == null) { - //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载 - try { - if (parent != null) { - //如果存在父类加载器,就委派给父类加载器加载 - c = parent.loadClass(name, false); - } else { - //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name) - c = findBootstrapClass0(name); - } - } catch (ClassNotFoundException e) { - // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能 - c = findClass(name); - } - } - if (resolve) { - resolveClass(c); - } - return c; - } - ``` - - - **双亲委派优势** - - 系统类防止内存中出现多份同样的字节码 - - 保证Java程序安全稳定运行 - - ### 自定义类加载器 - - 通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java 类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器就无法对其进行加载,这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自 ClassLoader 类,从上面对 loadClass 方法来分析来看,我们只需要重写 findClass 方法即可。 - - 注意: - - - 这里传递的文件名需要是类的全限定性名称,即`com.pdai.jvm.classloader.Test2`格式的,因为 defineClass 方法是按这种格式进行处理的。 - - 最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。 - - 这类Test 类本身可以被 AppClassLoader 类加载,因此我们不能把com/pdai/jvm/classloader/Test2.class 放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过我们自定义类加载器来加载。 \ No newline at end of file diff --git a/Jvm/src/T1.java b/Jvm/src/T1.java deleted file mode 100644 index 39d8feef..00000000 --- a/Jvm/src/T1.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @program JavaBooks - * @description: 模拟栈溢出 - * @author: mf - * @create: 2019/12/31 13:46 - */ - -/** - * 模拟栈溢出,虚拟机栈,本地方法栈 - */ -public class T1 { - - public void m() { - System.out.println("stack test overflow..."); - m(); - } - - public static void main(String[] args) { - new T1().m(); - } -} diff --git a/Jvm/src/T2.java b/Jvm/src/T2.java deleted file mode 100644 index 7a1d05eb..00000000 --- a/Jvm/src/T2.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @program JavaBooks - * @description: 引用计数的循环引用问题 - * @author: mf - * @create: 2020/01/19 12:53 - */ - -public class T2 { - public static void main(String[] args) { - Object object1 = new Object(); - Object object2 = new Object(); - object1 = object2; // obj1 引用obj2 - object2 = object1; // obj2 引用obj1 循环引用 - object1 = null; - object2 = null; - } -} diff --git a/LeetCode/README.md b/LeetCode/README.md deleted file mode 100644 index 298035f0..00000000 --- a/LeetCode/README.md +++ /dev/null @@ -1,55 +0,0 @@ -## LeetCode -### easy -- [1、两数之和](/LeetCode/src/normal/TwoSum.java) -- [7、整数反转](/LeetCode/src/normal/Reverse.java) -- [13、罗马数字转整数](/LeetCode/src/normal/RomanToInt.java) -- [13、最长公共前缀](/LeetCode/src/normal/LongestCommonPrefix.java) -- [20、有效的括号](/LeetCode/src/normal/IsValid.java) -- [21、合并两个有序链表](/LeetCode/src/normal/MergeTwoLists.java) -- [26、删除排序数组中的重复项](/LeetCode/src/normal/RemoveDuplicates.java) -- [28、strStr](/LeetCode/src/normal/StrStr.java) -- [53、最大子序和](/LeetCode/src/normal/MaxSubArray.java) -- [66、加一](/LeetCode/src/normal/PlusOne.java) -- [69、x的平方根](/LeetCode/src/normal/MySqrt.java) -- [70、爬楼梯](/LeetCode/src/normal/ClimbStairs.java) -- [88、合并两个排序的数组](/LeetCode/src/normal/Merge.java) -- [101、对称二叉树](/LeetCode/src/normal/IsSymmetric.java) -- [104、二叉树的最大深度](/LeetCode/src/normal/MaxDepth.java) -- [108、将有序数组转换为二叉搜索树](/LeetCode/src/normal/SortedArrayToBST.java) -- [111、二叉树的最小深度](/LeetCode/src/normal/MinDepth.java) -- [118、杨辉三角](/LeetCode/src/normal/Generate.java) -- [121、买卖股票的最佳时机](/LeetCode/src/normal/MaxProfit.java) -- [122、买卖股票的最佳时机2](/LeetCode/src/normal/MaxProfi2t.java) -- [125、验证回文串](/LeetCode/src/normal/IsPalindromeStr.java) -- [136、只出现一次的数字](/LeetCode/src/normal/SingleNumber.java) -- [141、环形链表](/LeetCode/src/normal/HasCycle.java) -- [155、最小栈](/LeetCode/src/normal/MinStack.java) -- [160、相交链表](/LeetCode/src/normal/GetIntersectionNode.java) -- [169、求众数](/LeetCode/src/normal/MajorityElement.java) -- [171、Excel表列序号](/LeetCode/src/normal/TitleToNumber.java) -- [172、阶乘后的零](/LeetCode/src/normal/TrailingZeroes.java) -- [189、旋转数组](/LeetCode/src/normal/Rotate.java) -- [190、颠倒二进制位](/LeetCode/src/normal/ReverseBits.java) -- [191、位1的个数](/LeetCode/src/normal/HammingWeight.java) -- [198、打家劫舍](/LeetCode/src/normal/Rob.java) -- [202、快乐数](/LeetCode/src/normal/IsHappy.java) -- [204、计算质数](/LeetCode/src/normal/CountPrimes.java) -- [206、反转链表](/LeetCode/src/normal/ReverseList.java) -- [217、存在重复元素](/LeetCode/src/normal/ContainsDuplicate.java) -- [225、用队列实现栈](/LeetCode/src/normal/MyStack.java) -- [232、用栈实现队列](/LeetCode/src/normal/MyQueue.java) -- [234、回文链表](/LeetCode/src/normal/isPalindromeNode.java) -- [242、有效的字母异位词](/LeetCode/src/normal/IsAnagram.java) -- [268、缺失数字](/LeetCode/src/normal/MissingNumber.java) -- [283、移动零](/LeetCode/src/normal/MoveZeroes.java) -- [326、3的幂](/LeetCode/src/normal/IsPowerOfThree.java) -- [344、反转字符串](/LeetCode/src/normal/ReverseString.java) -- [350、两个数组的交集 II](/LeetCode/src/normal/Intersect.java) -- [371、两整数之和](/LeetCode/src/normal/GetSum.java) -- [387、字符串中的第一个唯一字符](/LeetCode/src/normal/FirstUniqChar.java) -- [412、Fizz Buzz](/LeetCode/src/normal/FizzBuzz.java) -- [744、寻找比目标字母大的最小字母](/LeetCode/src/normal/NextGreatestLetter.java) - -### Medium -- [2、两个链表相加](/LeetCode/src/normal/AddTwoNumbers.java) -- [3、无重复字符的最长子串](/LeetCode/src/normal/LengthOfLongestSubstring.java) \ No newline at end of file diff --git a/LeetCode/src/normal/AddTwoNumbers.java b/LeetCode/src/normal/AddTwoNumbers.java deleted file mode 100644 index 8913f125..00000000 --- a/LeetCode/src/normal/AddTwoNumbers.java +++ /dev/null @@ -1,68 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 两数相加 - * @author: mf - * @create: 2019/10/17 00:53 - */ - -/** - * 给出两个 非空 的链表用来表示两个非负的整数。 - * 其中,它们各自的位数是按照 逆序 的方式存储的, - * 并且它们的每个节点只能存储 一位 数字。 - * 如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 - * 您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 - * 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) - * 输出:7 -> 0 -> 8 - * 原因:342 + 465 = 807 - */ - -/** - * 思路 - * 搞一个进位标志即可--carried - * 挺简单的,注意边界 - */ -/// 时间复杂度: O(n) -/// 空间复杂度: O(n) -/** - * Definition for singly-linked list. - * public class ListNode { - * int val; - * ListNode next; - * ListNode(int x) { val = x; } - * } - */ -public class AddTwoNumbers { - public static void main(String[] args) { - int[] arr = {2, 4, 3}; - int[] arr1 = {5, 6, 4}; - ListNode l1 = ListNode.setListNode(arr); - ListNode l2 = ListNode.setListNode(arr1); - ListNode l3 = addTwoNumbers(l1, l2); - ListNode.printNode(l3); - } - - private static ListNode addTwoNumbers(ListNode l1, ListNode l2) { - if (l1 == null && l2 == null) return null; - if (l1 == null) return l2; - if (l2 == null) return l1; - ListNode p1 = l1; - ListNode p2 = l2; - ListNode l3 = new ListNode(-1); - ListNode p3 = l3; - int carried = 0; - while (p1 != null || p2 != null) { - // 两个链表长度不一定相等 - int a = p1 != null ? p1.val : 0; - int b = p2 != null ? p2.val : 0; - p3.next = new ListNode((a + b + carried) % 10); - carried = (a + b + carried) / 10; - p3 = p3.next; - // 两个链表长度不一定相等 - p1 = p1 != null ? p1.next : null; - p2 = p2 != null ? p2.next : null; - } - // 当两个末位正好进位,并且next都为空 - p3.next = carried != 0 ? new ListNode(1) : null; - return l3.next; - } -} diff --git a/LeetCode/src/normal/ClimbStairs.java b/LeetCode/src/normal/ClimbStairs.java deleted file mode 100644 index e0e6425e..00000000 --- a/LeetCode/src/normal/ClimbStairs.java +++ /dev/null @@ -1,44 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 70. 爬楼梯 - * @author: mf - * @create: 2019/11/10 12:52 - */ - -/* -题目:https://leetcode-cn.com/problems/climbing-stairs/ -难度:easy -类型:动态规划 - */ -/* -输入: 2 -输出: 2 -解释: 有两种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 -2. 2 阶 - -输入: 3 -输出: 3 -解释: 有三种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 + 1 阶 -2. 1 阶 + 2 阶 -3. 2 阶 + 1 阶 - */ -public class ClimbStairs { - public static void main(String[] args) { - System.out.println(climbStairs(3)); - } - - private static int climbStairs(int n) { - if (n <= 2) return n; - int pre2 = 1, pre1 = 2; - for (int i = 3; i <= n; i++) { - int cur = pre1 + pre2; - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} diff --git a/LeetCode/src/normal/ContainsDuplicate.java b/LeetCode/src/normal/ContainsDuplicate.java deleted file mode 100644 index ec3516fb..00000000 --- a/LeetCode/src/normal/ContainsDuplicate.java +++ /dev/null @@ -1,41 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 217.存在重复元素 - * @author: mf - * @create: 2019/11/04 16:24 - */ - -import java.util.HashMap; - -/** - * 题目:https://leetcode-cn.com/problems/contains-duplicate/ - * 难度:easy - * 类型:数组 - */ -/* -输入: [1,2,3,1] -输出: true - -输入: [1,2,3,4] -输出: false - */ -public class ContainsDuplicate { - public static void main(String[] args) { - int[] nums = {1,2,3,1}; - int[] nums1 = {1,2,3,4}; - int[] nums2 = {0,4,5,0,3,6}; - int[] nums3 = {2,14,18,22,22}; - System.out.println(containsDuplicate(nums1)); - } - public static boolean containsDuplicate(int[] nums) { - HashMap map = new HashMap<>(); - for (int num : nums) { - if (map.containsKey(num)) { - return true; - } else { - map.put(num, 1); - } - } - return false; - } -} diff --git a/LeetCode/src/normal/CountPrimes.java b/LeetCode/src/normal/CountPrimes.java deleted file mode 100644 index 6ae8c220..00000000 --- a/LeetCode/src/normal/CountPrimes.java +++ /dev/null @@ -1,42 +0,0 @@ -package normal; - -import java.util.Arrays; -import java.util.HashSet; - -/** - * @program JavaBooks - * @description: 204.计算质数 - * @author: mf - * @create: 2019/11/06 10:44 - */ - -/* -题目:https://leetcode-cn.com/problems/count-primes/ -类型:筛选 -难度:easy - */ -/* -输入: 10 -输出: 4 -解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。s - */ -public class CountPrimes { - public static void main(String[] args) { - System.out.println(countPrimes(10)); - } - - private static int countPrimes(int n) { - if (n == 0 || n == 1) return 0; - boolean[] num = new boolean[n + 1]; - int count = 0; - for (int i = 2; i < n; i++) { - if (num[i] == false) { - count++; - for (int j = 2 * i; j < n + 1; j += i) { - num[j] = true; - } - } - } - return count; - } -} diff --git a/LeetCode/src/normal/FindMedianSortedArrays.java b/LeetCode/src/normal/FindMedianSortedArrays.java deleted file mode 100644 index 0ab2c5e7..00000000 --- a/LeetCode/src/normal/FindMedianSortedArrays.java +++ /dev/null @@ -1,62 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 两个有序数组的中位数 - * @author: mf - * @create: 2019/10/19 22:08 - */ - -/** - * 请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。 - * 你可以假设 nums1 和 nums2 不会同时为空。 - * nums1 = [1, 3] - * nums2 = [2] - * 则中位数是 2.0 - * nums1 = [1, 2] - * nums2 = [3, 4] - * 则中位数是 (2 + 3)/2 = 2.5 - */ - -/** - * 具体思路如下,将问题 转化为在两个数组中找第 K 个小的数 。 - * A[k/2] = B[k/2],那么第 k 大的数就是 A[k/2] - * A[k/2] > B[k/2],那么第 k 大的数肯定在 A[0:k/2+1] 和 B[k/2:] 中,这样就将原来的所有数的总和减少到一半了,再在这个范围里面找第 k/2 大的数即可,这样也达到了二分查找的区别了。 - * A[k/2] < B[k/2],那么第 k 大的数肯定在 B[0:k/2+1]和 A[k/2:] 中,同理在这个范围找第 k/2 大的数就可以了。 - */ - -public class FindMedianSortedArrays { - public static void main(String[] args) { - int[] A = {1,3,4,7}; - int[] B = {1,2,3,4,5,6,7,8,9,10}; - double res = findMedianSortedArrays(A, B); - System.out.println(res); - - } - - private static double findMedianSortedArrays(int[] nums1, int[] nums2) { - int n = nums1.length; - int m = nums2.length; - int left = (n + m + 1) / 2; - int right = (n + m + 2) / 2; - //一个小技巧:将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。 - return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5; - } - private static int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) { - int len1 = end1 - start1 + 1; - int len2 = end2 - start2 + 1; - //让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1 - if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k); - if (len1 == 0) return nums2[start2 + k - 1]; - - if (k == 1) return Math.min(nums1[start1], nums2[start2]); - - int i = start1 + Math.min(len1, k / 2) - 1; - int j = start2 + Math.min(len2, k / 2) - 1; - - if (nums1[i] > nums2[j]) { - return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1)); - } - else { - return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1)); - } - } -} diff --git a/LeetCode/src/normal/FirstUniqChar.java b/LeetCode/src/normal/FirstUniqChar.java deleted file mode 100644 index 8c7957b4..00000000 --- a/LeetCode/src/normal/FirstUniqChar.java +++ /dev/null @@ -1,44 +0,0 @@ -package normal; - -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 387.字符串中的第一个唯一字符 - * @author: mf - * @create: 2019/11/06 16:59 - */ - -/* -题目:https://leetcode-cn.com/problems/first-unique-character-in-a-string/ -类型: -难度:easy - */ - -/* -s = "leetcode" -返回 0. - -s = "loveleetcode", -返回 2. - */ -public class FirstUniqChar { - public static void main(String[] args) { - String s = "leetcode"; - String s1 = "loveleetcode"; - System.out.println(firstUniqChar(s1)); - } - - private static int firstUniqChar(String s) { - HashMap map = new HashMap<>(); - for (char c : s.toCharArray()) { - map.put(c, map.getOrDefault(c, 0) + 1); - } - for (int i = 0; i < s.length(); i++) { - if (map.get(s.charAt(i)) == 1) { - return i; - } - } - return -1; - } -} diff --git a/LeetCode/src/normal/FizzBuzz.java b/LeetCode/src/normal/FizzBuzz.java deleted file mode 100644 index 7428c97b..00000000 --- a/LeetCode/src/normal/FizzBuzz.java +++ /dev/null @@ -1,62 +0,0 @@ -package normal; - -import java.util.ArrayList; -import java.util.List; - -/** - * @program JavaBooks - * @description: 412. Fizz Buzz - * @author: mf - * @create: 2019/11/10 22:29 - */ - -/* -题目:https://leetcode-cn.com/problems/fizz-buzz/ -难度:easy - */ - -/* -n = 15, - -返回: -[ - "1", - "2", - "Fizz", - "4", - "Buzz", - "Fizz", - "7", - "8", - "Fizz", - "Buzz", - "11", - "Fizz", - "13", - "14", - "FizzBuzz" -] - */ -public class FizzBuzz { - public static void main(String[] args) { - List ans = fizzBuzz(15); - System.out.println(ans.toString()); - } - - public static List fizzBuzz(int n) { - List ans = new ArrayList<>(); - if (n == 0) return ans; - for (int i = 1; i <= n; i++) { - if ((i % 3 == 0) && (i % 5 == 0)) { - ans.add("FizzBuzz"); - } else if (i % 3 == 0) { - ans.add("Fizz"); - } else if (i % 5 == 0) { - ans.add("Buzz"); - } else { - ans.add(String.valueOf(i)); - } - } - return ans; - } -} diff --git a/LeetCode/src/normal/Generate.java b/LeetCode/src/normal/Generate.java deleted file mode 100644 index 63fc08c3..00000000 --- a/LeetCode/src/normal/Generate.java +++ /dev/null @@ -1,62 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 118.杨辉三角 - * @author: mf - * @create: 2019/11/03 16:38 - */ - -import java.util.ArrayList; -import java.util.List; - -/** - * 难度:easy - * 类别:数组 - * 链接:https://leetcode-cn.com/problems/pascals-triangle/ - * 思路:分析规律 - * */ - -/** - * 题目描述 - * 输入:5 - * 输出: - * [ - * [1], - * [1,1], - * [1,2,1], - * [1,3,3,1], - * [1,4,6,4,1] - * ] - */ - -public class Generate { - public static void main(String[] args) { - List> res = generate(5); - for (List re : res) { - System.out.println(re.toString()); - } - } - - private static List> generate(int numRows) { - List> ans = new ArrayList<>(); - for (int i = 0; i < numRows; i++) { - List curRow = new ArrayList<>(); - for (int j = 0; j <= i ; j++) { - // 第一个元素和最后一个元素 - if (j == 0 || j == i){ - curRow.add(1); - continue; - } - if (i == 0 || i == 1) { - // 前两行没得中间元素 - continue; - } - List preRow = ans.get(i - 1); - // 处理中间元素 - int value = preRow.get(j - 1) + preRow.get(j); - curRow.add(value); - } - ans.add(curRow); - } - return ans; - } -} diff --git a/LeetCode/src/normal/GetIntersectionNode.java b/LeetCode/src/normal/GetIntersectionNode.java deleted file mode 100644 index 11bc9016..00000000 --- a/LeetCode/src/normal/GetIntersectionNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 160.相交链表 - * @author: mf - * @create: 2019/11/05 10:36 - */ -/* -题目:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/ -难度:easy -类型:链表 - */ - -/* -A: a1 → a2 - ↘ - c1 → c2 → c3 - ↗ -B: b1 → b2 → b3 - */ -public class GetIntersectionNode { - public static void main(String[] args) { - - } - - public static ListNode getIntersectionNode(ListNode headA, ListNode headB) { - ListNode l1 = headA, l2 = headB; - while (l1 != l2) { - l1 = (l1 == null) ? headB : l1.next; - l2 = (l2 == null) ? headA : l2.next; - } - return l1; - } - -} diff --git a/LeetCode/src/normal/GetSum.java b/LeetCode/src/normal/GetSum.java deleted file mode 100644 index d1e280e7..00000000 --- a/LeetCode/src/normal/GetSum.java +++ /dev/null @@ -1,28 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 371. 两整数之和 - * @author: mf - * @create: 2019/11/09 13:58 - */ - -/* -题目:https://leetcode-cn.com/problems/sum-of-two-integers/ -难度:easy - */ - -/* -不使用运算符 + 和 - ​​​​​​​,计算两整数 ​​​​​​​a 、b ​​​​​​​之和。 -输入: a = 1, b = 2 -输出: 3 - */ -public class GetSum { - public static void main(String[] args) { - System.out.println(getSum(5,3)); - } - - private static int getSum(int a, int b) { - return b == 0 ? a : getSum(a ^ b, (a & b) << 1); - } -} diff --git a/LeetCode/src/normal/HammingWeight.java b/LeetCode/src/normal/HammingWeight.java deleted file mode 100644 index cfefe745..00000000 --- a/LeetCode/src/normal/HammingWeight.java +++ /dev/null @@ -1,27 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 191. 位1的个数 - * @author: mf - * @create: 2019/11/09 12:35 - */ - -/* -题目:https://leetcode-cn.com/problems/number-of-1-bits/ -难度:easy - */ - -public class HammingWeight { - public static void main(String[] args) { - System.out.println(hammingWeight(15)); - } - public static int hammingWeight(int n) { - int ans = 0; - while (n != 0) { - n &= n - 1; - ans++; - } - return ans; - } -} diff --git a/LeetCode/src/normal/HasCycle.java b/LeetCode/src/normal/HasCycle.java deleted file mode 100644 index 5b19f852..00000000 --- a/LeetCode/src/normal/HasCycle.java +++ /dev/null @@ -1,61 +0,0 @@ -package normal; - -import java.util.HashSet; - -/** - * @program JavaBooks - * @description: 141.环形链表 - * @author: mf - * @create: 2019/11/05 10:19 - */ - -/* -题目: -难度:easy -类型:链表 - */ -public class HasCycle { - public static void main(String[] args) { - - } - - /** - * 哈希 - * @param head - * @return - */ - public static boolean hasCycle(ListNode head) { - HashSet set = new HashSet<>(); - while (head != null) { - if (set.contains(head)) { - return true; - } else { - set.add(head); - } - head = head.next; - } - return false; - } - - /** - * 快慢指针 - * @param head - * @return - */ - public static boolean hasCycle2(ListNode head) { - if (head != null && head.next != null) { - ListNode quick = head; - ListNode slow = head; - while (2 > 1) { - quick = quick.next; - if (quick == null) return false; - quick = quick.next; - if (quick == null) return false; - slow = slow.next; - if (slow == quick) return true; - } - } else { - return false; - } - } -} diff --git a/LeetCode/src/normal/Intersect.java b/LeetCode/src/normal/Intersect.java deleted file mode 100644 index 69e2597d..00000000 --- a/LeetCode/src/normal/Intersect.java +++ /dev/null @@ -1,77 +0,0 @@ -package normal; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 350.两个数组的交集2 - * @author: mf - * @create: 2019/11/06 16:14 - */ - -/* -题目:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/ -类型:哈希和集合 -难度:easy - */ -public class Intersect { - public static void main(String[] args) { - int[] nums1 = {1,2,2,1}; - int[] nums2 = {2,2}; - int[] ans = intersect2(nums1, nums2); - System.out.println(Arrays.toString(ans)); - } - - /** - * 集合 - * @param nums1 - * @param nums2 - * @return - */ - private static int[] intersect(int[] nums1, int[] nums2) { - ArrayList list1 = new ArrayList<>(); - for (int num : nums1) { - list1.add(num); - } - ArrayList list2 = new ArrayList<>(); - for (int num : nums2) { - if (list1.contains(num)) { - list2.add(num); - list1.remove(num); - } - } - int[] ans = new int[list2.size()]; - for (int i = 0; i < list2.size(); i++) { - ans[i] = list2.get(i); - } - return ans; - } - - /** - * 哈希 - * @param nums1 - * @param nums2 - * @return - */ - private static int[] intersect2(int[] nums1, int[] nums2) { - HashMap map = new HashMap<>(); - for (int num : nums1) { - map.put(num, map.getOrDefault(num, 0) + 1); - } - ArrayList list = new ArrayList<>(); - for (int num : nums2) { - Integer count = map.get(num); - if (count != null && count != 0) { - list.add(num); - map.put(num, --count); - } - } - int[] ans = new int[list.size()]; - for (int i = 0; i < list.size(); i++) { - ans[i] = list.get(i); - } - return ans; - } -} diff --git a/LeetCode/src/normal/IsAnagram.java b/LeetCode/src/normal/IsAnagram.java deleted file mode 100644 index 0a0e9179..00000000 --- a/LeetCode/src/normal/IsAnagram.java +++ /dev/null @@ -1,78 +0,0 @@ -package normal; - -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 242.有效的字母异位词 - * @author: mf - * @create: 2019/11/06 15:19 - */ - -/* -题目:https://leetcode-cn.com/problems/valid-anagram/ -类型:哈希等 -难度:easy - */ - -/* -输入: s = "anagram", t = "nagaram" -输出: true -输入: s = "rat", t = "car" -输出: false - */ -public class IsAnagram { - public static void main(String[] args) { - String s = "anagram"; - String t = "nagaram"; - System.out.println(isAnagram(s, t)); - System.out.println(isAnagram2(s, t)); - } - - /** - * 普通字符串方法 - * @param s - * @param t - * @return - */ - private static boolean isAnagram(String s, String t) { - int[] sCount = new int[26]; - int[] tCount = new int[26]; - for (char ch : s.toCharArray()) { - sCount[ch - 'a']++; - } - for (char c : t.toCharArray()) { - tCount[c - 'a']++; - } - for (int i = 0; i < 26; i++) { - if (sCount[i] != tCount[i]) { - return false; - } - } - return true; - } - - /** - * 哈希 - * @param s - * @param t - * @return - */ - private static boolean isAnagram2(String s, String t) { - HashMap map = new HashMap<>(); - for (char c : s.toCharArray()) { - map.put(c, map.getOrDefault(c, 0) + 1); - } - for (char c : t.toCharArray()) { - Integer count = map.get(c); - if (count == null){ - return false; - } else if (count > 1) { - map.put(c, count - 1); - } else { - map.remove(c); - } - } - return map.isEmpty(); - } -} diff --git a/LeetCode/src/normal/IsHappy.java b/LeetCode/src/normal/IsHappy.java deleted file mode 100644 index ec2289c1..00000000 --- a/LeetCode/src/normal/IsHappy.java +++ /dev/null @@ -1,51 +0,0 @@ -package normal; - -import java.util.HashSet; - -/** - * @program JavaBooks - * @description: 202.快乐数 - * @author: mf - * @create: 2019/11/06 10:15 - */ - - -public class IsHappy { - public static void main(String[] args) { - System.out.println(isHappy(19)); - System.out.println(isHappy2(19)); - } - - /** - * 递归,但有4的话,就一直循环,所以是4就false - * @param n - * @return - */ - private static boolean isHappy(int n) { - if (n == 1) return true; - if (n != 4) { - int sum = 0, k = n; - while (k > 0) { - sum += (k % 10) * (k % 10); - k /= 10; - } - return isHappy(sum); - } - return false; - } - - private static boolean isHappy2(int n) { - if (n == 1) return true; - HashSet set = new HashSet<>(); - while (2 > 1) { - int sum = 0; - while (n > 0) { - sum += (n % 10) * (n % 10); - n /= 10; - } - if (sum == 1) return true; - if (!set.add(sum)) return false; - n = sum; - } - } -} diff --git a/LeetCode/src/normal/IsPalindrome.java b/LeetCode/src/normal/IsPalindrome.java deleted file mode 100644 index 53dcc573..00000000 --- a/LeetCode/src/normal/IsPalindrome.java +++ /dev/null @@ -1,48 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 回文数 - * @author: mf - * @create: 2019/10/20 17:33 - */ - -/** - * 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 - * 输入: 121 - * 输出: true - * 输入: -121 - * 输出: false - * 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 - * 输入: 10 - * 输出: false - * 解释: 从右向左读, 为 01 。因此它不是一个回文数。 - */ - -/** - * 通过取整和取余操作获取整数中对应的数字进行比较。 - * 举个例子:1221 这个数字。 - * 通过计算 1221 / 1000, 得首位1 - * 通过计算 1221 % 10, 可得末位 1 - * 进行比较 - * 再将 22 取出来继续比较 - */ -public class IsPalindrome { - public static void main(String[] args) { - boolean res = isPalindrome(1221); - System.out.println(res); - } - - private static boolean isPalindrome(int x) { - // 边界判断 为负数直接false - if (x < 0) return false; - int div = 1; - while (x / div >= 10) div *= 10; - while (x > 0) { - int left = x / div; - int right = x % 10; - if (left != right) return false; - x = (x % div) / 10; - div /= 100; - } - return true; - } -} diff --git a/LeetCode/src/normal/IsPalindromeStr.java b/LeetCode/src/normal/IsPalindromeStr.java deleted file mode 100644 index e13d75e0..00000000 --- a/LeetCode/src/normal/IsPalindromeStr.java +++ /dev/null @@ -1,39 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 125. 验证回文串 - * @author: mf - * @create: 2019/11/09 19:27 - */ - -/* -题目:https://leetcode-cn.com/problems/valid-palindrome/ -难度:easy - */ -public class IsPalindromeStr { - public static void main(String[] args) { - String s = "A man, a plan, a canal: Panama"; - String s1 = "ab2a"; - System.out.println(isPalindrome(s1)); - } - public static boolean isPalindrome(String s) { - if (s.equals("")) return true; - s = s.toLowerCase(); - char[] sChar = s.toCharArray(); - int l = 0, r = sChar.length - 1; - while (l <= r) { - if (sChar[l] == sChar[r]) { - l++; - r--; - } else if (!((sChar[l] >= 'a' && sChar[l] <= 'z') || (sChar[l] >= '0' && sChar[l] <= '9'))) { - l++; - } else if (!((sChar[r] >= 'a' && sChar[r] <= 'z') || (sChar[r] >= '0' && sChar[r] <= '9'))) { - r--; - } else { - return false; - } - } - return true; - } -} diff --git a/LeetCode/src/normal/IsPowerOfThree.java b/LeetCode/src/normal/IsPowerOfThree.java deleted file mode 100644 index 9fc91a33..00000000 --- a/LeetCode/src/normal/IsPowerOfThree.java +++ /dev/null @@ -1,34 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 326. 3的幂 - * @author: mf - * @create: 2019/11/10 21:42 - */ - -/* -题目:https://leetcode-cn.com/problems/power-of-three/ -难度:easy - */ -/* -输入: 27 -输出: true -输入: 0 -输出: false - */ -public class IsPowerOfThree { - public static void main(String[] args) { - System.out.println(isPowerOfThree(45)); - } - - public static boolean isPowerOfThree(int n) { - while (n > 1) { - if (n % 3 != 0) { - return false; - } - n /= 3; - } - return true; - } -} diff --git a/LeetCode/src/normal/IsSymmetric.java b/LeetCode/src/normal/IsSymmetric.java deleted file mode 100644 index 999ba32b..00000000 --- a/LeetCode/src/normal/IsSymmetric.java +++ /dev/null @@ -1,40 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 101.对称二叉树 - * @author: mf - * @create: 2019/11/08 10:32 - */ - -/* -题目:https://leetcode-cn.com/problems/symmetric-tree/ -类型:二叉树 -难度:easy - */ -/* - 1 - / \ - 2 2 - / \ / \ -3 4 4 3 - */ - -// 思路: - // 对称二叉树的前序遍历是一样的 -public class IsSymmetric { - public static void main(String[] args) { - int[] pre = {1,2,3,4,2,4,3}; - int[] in = {3,2,4,1,4,2,3}; - TreeNode tree = TreeNode.setBinaryTree(pre, in); - System.out.println(isSymmetric(tree, tree)); - } - - private static boolean isSymmetric(TreeNode tree, TreeNode tree1) { - if (tree == null && tree1 == null) return true; // 遍历到底了 - if (tree == null || tree1 == null) return false; // 说明二叉树不完整 - if (tree.val != tree1.val) return false; // 说明值不相等,不对称 - // node.left right 前序, node1 right left 对称前序遍历 - return isSymmetric(tree.left, tree1.right) && isSymmetric(tree.right, tree1.left); - } -} diff --git a/LeetCode/src/normal/IsValid.java b/LeetCode/src/normal/IsValid.java deleted file mode 100644 index 091eedf2..00000000 --- a/LeetCode/src/normal/IsValid.java +++ /dev/null @@ -1,48 +0,0 @@ -package normal; - -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 20.有效的括号 - * @author: mf - * @create: 2019/11/07 10:20 - */ - -/* -题目:https://leetcode-cn.com/problems/valid-parentheses/ -类型:栈 -难度:easy - */ -/* -输入: "()" -输出: true -输入: "()[]{}" -输出: true -输入: "(]" -输出: false - */ -public class IsValid { - public static void main(String[] args) { - String s = "()"; - System.out.println(isValid(s)); - } - - private static boolean isValid(String s) { - Stack stack = new Stack<>(); - for (char c : s.toCharArray()) { - if (stack.size() == 0) { - stack.push(c); - } else if (isSym(stack.peek(), c)) { - stack.pop(); - } else { - stack.push(c); - } - } - return stack.size() == 0; - - } - private static boolean isSym(char c1, char c2) { - return (c1 == '(' && c2 == ')') || (c1 == '[' && c2 == ']') || (c1 == '{' && c2 == '}'); - } -} diff --git a/LeetCode/src/normal/LengthOfLongestSubstring.java b/LeetCode/src/normal/LengthOfLongestSubstring.java deleted file mode 100644 index d23ef6ba..00000000 --- a/LeetCode/src/normal/LengthOfLongestSubstring.java +++ /dev/null @@ -1,54 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 无重复字符的最长子串 - * @author: mf - * @create: 2019/10/17 17:18 - */ - -/** - * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 - * 输入: "abcabcbb" - * 输出: 3 - * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 - * 输入: "bbbbb" - * 输出: 1 - * 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 - * 输入: "pwwkew" - * 输出: 3 - * 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 - */ - -/** - * 滑动窗口 - * 时间复杂度: O(len(s)) - * 空间复杂度: O(len(charset)) - */ - -public class LengthOfLongestSubstring { - public static void main(String[] args) { - int length = lengthOfLongestSubstring("abccacbb"); - System.out.println(length); - } - - private static int lengthOfLongestSubstring(String s) { - int[] freq = new int[256]; - int l = 0; - int r = -1; // 滑动窗口为s[l...r] - int res = 0; - // 整个循环从 l == 0 ; r == -1 这个空窗口开始 - // 到l == s.size(); r == s.szie() - 1这个空窗口截止 - // 在每次循环里逐渐改变窗口,维护frep,并记录当前窗口是否找到了一个新的最优值 - while (l < s.length()) { - if (r + 1 < s.length() && freq[s.charAt(r + 1)] == 0) { - r++; - freq[s.charAt(r)]++; - } else { - // r已经到头 || freq[s.charAt(r + 1)] == 1 - freq[s.charAt(l)]--; - l++; - } - res = Math.max(res, r - l + 1); - } - return res; - } -} diff --git a/LeetCode/src/normal/ListNode.java b/LeetCode/src/normal/ListNode.java deleted file mode 100644 index 9f07e1b0..00000000 --- a/LeetCode/src/normal/ListNode.java +++ /dev/null @@ -1,36 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 链表 - * @author: mf - * @create: 2019/10/17 01:03 - */ - -public class ListNode { - int val; - ListNode next; - - public ListNode(int val) { - this.val = val; - } - - public static ListNode setListNode(int[] arr) { - ListNode node = new ListNode(-1); - ListNode p1 = node; - for (int num : arr) { - ListNode temp = new ListNode(num); - p1.next = temp; - p1 = p1.next; - } - return node.next; - } - - public static void printNode(ListNode node) { - while (node != null) { - System.out.print(node.val + "->"); - node = node.next; - } - System.out.print("null"); - } -} diff --git a/LeetCode/src/normal/LongestCommonPrefix.java b/LeetCode/src/normal/LongestCommonPrefix.java deleted file mode 100644 index 91927ecb..00000000 --- a/LeetCode/src/normal/LongestCommonPrefix.java +++ /dev/null @@ -1,27 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 14. 最长公共前缀 - * @author: mf - * @create: 2019/11/10 16:09 - */ - -public class LongestCommonPrefix { - public static void main(String[] args) { - String[] strs = {"flower","flow","flight"}; - System.out.println(longestCommonPrefix(strs)); - } - - public static String longestCommonPrefix(String[] strs) { - if (strs.length == 0) return ""; - String str = strs[0]; - for (int i = 1; i < strs.length; i++) { - while(strs[i].indexOf(str) != 0) { - str = str.substring(0, str.length() - 1); - } - } - return str; - } - -} diff --git a/LeetCode/src/normal/MajorityElement.java b/LeetCode/src/normal/MajorityElement.java deleted file mode 100644 index 8d60d993..00000000 --- a/LeetCode/src/normal/MajorityElement.java +++ /dev/null @@ -1,42 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 169.求众数 - * @author: mf - * @create: 2019/11/04 15:25 - */ - -/** - * 题目:https://leetcode-cn.com/problems/majority-element/ - * 难度:easy - */ - -/* -输入: [3,2,3] -输出: 3 - -输入: [2,2,1,1,1,2,2] -输出: 2 - */ -public class MajorityElement { - public static void main(String[] args) { - int[] arr = {3,2,3}; - int[] arr1 = {2,2,1,1,1,2,2}; - System.out.println(majorityElement(arr1)); - } - - private static int majorityElement(int[] nums) { - int count = 1; - int ans = nums[0]; - for (int i = 1; i < nums.length; i++) { - if (nums[i] == ans) { - count++; - } else { - count--; - if (count == 0) { - ans = nums[i + 1]; - } - } - } - return ans; - } -} diff --git a/LeetCode/src/normal/MaxDepth.java b/LeetCode/src/normal/MaxDepth.java deleted file mode 100644 index 8a850154..00000000 --- a/LeetCode/src/normal/MaxDepth.java +++ /dev/null @@ -1,32 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 104. 二叉树的最大深度 - * @author: mf - * @create: 2019/11/08 15:23 - */ - -/* -题目:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/ -类型:树 -难度:easy - */ -/* -给定二叉树 [3,9,20,null,null,15,7], - - 3 - / \ - 9 20 - / \ - 15 7 - */ -public class MaxDepth { - public static void main(String[] args) { - - } - - private static int maxDepth(TreeNode root) { - return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; - } -} diff --git a/LeetCode/src/normal/MaxProfit.java b/LeetCode/src/normal/MaxProfit.java deleted file mode 100644 index 3d8808f9..00000000 --- a/LeetCode/src/normal/MaxProfit.java +++ /dev/null @@ -1,53 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 121.买卖股票的最佳时机 - * @author: mf - * @create: 2019/11/04 14:22 - */ - -/** - * 题目:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/ - * 难度:easy - * 类型:数组 - */ - -/* -输入: [7,1,5,3,6,4] -输出: 5 -解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 - 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 -输入: [7,6,4,3,1] -输出: 0 -解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 - */ - -public class MaxProfit { - public static void main(String[] args) { - int[] arr = {7,1,5,3,6,4}; - int[] arr1 = {7,6,4,3,1}; - System.out.println(maxProfit(arr)); - } - // 动态规划 - public static int maxProfit(int[] prices) { - if (prices.length <= 1) return 0; - // 1. 记录之前买入的最小值 - int min = prices[0], max = 0; - for (int i = 1; i < prices.length; i++) { - max = Math.max(max, prices[i] - min); - min = Math.min(min, prices[i]); - } - return max; - - } - // 两次循环 -// public static int maxProfit(int[] prices) { -// int maxPro = 0; -// for (int i = 0; i < prices.length; i++) { -// for (int j = prices.length - 1; j > i; j--) { -// if (prices[i] < prices[j]) { -// maxPro = Math.max(maxPro, prices[j] - prices[i]); -// } -// } -// } -// return maxPro; -} diff --git a/LeetCode/src/normal/MaxProfit2.java b/LeetCode/src/normal/MaxProfit2.java deleted file mode 100644 index 1b402e44..00000000 --- a/LeetCode/src/normal/MaxProfit2.java +++ /dev/null @@ -1,39 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 122.买卖股票的最佳时机 - * @author: mf - * @create: 2019/11/04 14:59 - */ - -/** - * 题目:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ - * 难度:easy - * 类型:数组 || 贪心 - */ - -/* -输入: [7,1,5,3,6,4] -输出: 7 -解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 -  随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 -输入: [1,2,3,4,5] -输出: 4 -解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 -  注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 -  因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 - */ -public class MaxProfit2 { - public static void main(String[] args) { - int[] arr = {7,1,5,3,6,4}; - System.out.println(maxProfit2(arr)); - } - public static int maxProfit2(int[] prices) { - int ans = 0; - for (int i = 1; i < prices.length; i++) { - if (prices[i] > prices[i - 1]) { - ans += prices[i] - prices[i - 1]; - } - } - return ans; - } -} diff --git a/LeetCode/src/normal/MaxSubArray.java b/LeetCode/src/normal/MaxSubArray.java deleted file mode 100644 index 228742cb..00000000 --- a/LeetCode/src/normal/MaxSubArray.java +++ /dev/null @@ -1,37 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 53. 最大子序和 - * @author: mf - * @create: 2019/11/10 10:37 - */ - -/* -题目:https://leetcode-cn.com/problems/maximum-subarray/ -类型:动态规划 -难度:easy - */ - -/* -输入: [-2,1,-3,4,-1,2,1,-5,4], -输出: 6 -解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 - */ -public class MaxSubArray { - public static void main(String[] args) { - int[] nums = {-2,1,-3,4,-1,2,1,-5,4}; - System.out.println(maxSubArray(nums)); - } - - public static int maxSubArray(int[] nums) { - if (nums == null || nums.length == 0) return 0; - int max = nums[0]; // 记录包含arr[i]的连续子数组的和的最大值 - int ans = nums[0]; // 记录当前所有子数组的和的最大值 - for (int i = 1; i < nums.length; i++) { - max = Math.max(max + nums[i], nums[i]); - ans = Math.max(max, ans); - } - return ans; - } -} diff --git a/LeetCode/src/normal/Merge.java b/LeetCode/src/normal/Merge.java deleted file mode 100644 index 677fb5ee..00000000 --- a/LeetCode/src/normal/Merge.java +++ /dev/null @@ -1,52 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 88、合并两个有序数组 - * @author: mf - * @create: 2019/11/04 09:46 - */ - -/** - * 题目:https://leetcode-cn.com/problems/merge-sorted-array/ - * 难度:easy - */ - -import java.util.Arrays; - -/** - * 输入: - * nums1 = [1,2,3,0,0,0], m = 3 - * nums2 = [2,5,6], n = 3 - * - * 输出: [1,2,2,3,5,6] - * - */ -public class Merge { - public static void main(String[] args) { -// int[] nums1 = {1,2,3,0,0,0}; -// int[] nums2 = {2,5,6}; - int[] nums1 = {2,0}; - int[] nums2 = {1}; - merge(nums1, 1, nums2, 1); - System.out.println(Arrays.toString(nums1)); - } - // 可以双指针 - public static void merge(int[] nums1, int m, int[] nums2, int n) { - // 两种情况 - // 第一种,nums1全是0,也就是m==0 - if (m == 0) { - for (int i = 0; i < n; i++) { - nums1[i] = nums2[i]; - } - return; - } - // 第二种,m不是0的情况 - int p = nums1.length - 1; - int a1 = m - 1; - for (int i = n - 1; i >= 0; i--) { - while (a1 >= 0 && nums1[a1] > nums2[i]) { - nums1[p--] = nums1[a1--]; - } - nums1[p--] = nums2[i]; - } - } -} diff --git a/LeetCode/src/normal/MergeTwoLists.java b/LeetCode/src/normal/MergeTwoLists.java deleted file mode 100644 index cace28d3..00000000 --- a/LeetCode/src/normal/MergeTwoLists.java +++ /dev/null @@ -1,43 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 合并两个有序链表 - * @author: mf - * @create: 2019/11/05 09:55 - */ - -/* -题目:https://leetcode-cn.com/problems/merge-two-sorted-lists/ -难度:easy -类型:链表 -*/ - -/* -输入:1->2->4, 1->3->4 -输出:1->1->2->3->4->4 - */ - -// 一般链表都是递归 -public class MergeTwoLists { - public static void main(String[] args) { - int[] arr1 = {1 ,2 ,4}; - int[] arr2 = {1 ,3 ,4}; - ListNode l1 = ListNode.setListNode(arr1); - ListNode l2 = ListNode.setListNode(arr2); - ListNode l3 = mergeTwoLists(l1, l2); - ListNode.printNode(l3); - } - - private static ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; - if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1, l2.next); - return l2; - } - } -} diff --git a/LeetCode/src/normal/MinDepth.java b/LeetCode/src/normal/MinDepth.java deleted file mode 100644 index f43f8573..00000000 --- a/LeetCode/src/normal/MinDepth.java +++ /dev/null @@ -1,31 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 二叉树的最小深度 - * @author: mf - * @create: 2019/10/18 23:04 - */ - -/** - * 给定一个二叉树,找出其最小深度。 - * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 - */ -public class MinDepth { - public static void main(String[] args) { - int[] pre = {3, 9, 20, 15, 7}; - int[] in = {9, 3, 15, 20, 7}; - TreeNode node = TreeNode.setBinaryTree(pre, in); - int number = minDepth(node); - System.out.println(number); - } - - private static int minDepth(TreeNode node) { - if (node == null) return 0; - if (node.left == null && node.right == null) return 1; - int left, right; - if (node.left != null) left = minDepth(node.left); - else left = Integer.MAX_VALUE; - if (node.right != null) right = minDepth(node.right); - else right = Integer.MAX_VALUE; - return Math.min(left, right) + 1; - } -} diff --git a/LeetCode/src/normal/MinStack.java b/LeetCode/src/normal/MinStack.java deleted file mode 100644 index d50b84e7..00000000 --- a/LeetCode/src/normal/MinStack.java +++ /dev/null @@ -1,67 +0,0 @@ -package normal; - -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 155.最小栈 - * @author: mf - * @create: 2019/11/07 12:01 - */ - -/* -题目:https://leetcode-cn.com/problems/min-stack/ -类型:栈 -难度:easy - */ - -/* -MinStack minStack = new MinStack(); -minStack.push(-2); -minStack.push(0); -minStack.push(-3); -minStack.getMin(); --> 返回 -3. -minStack.pop(); -minStack.top(); --> 返回 0. -minStack.getMin(); --> 返回 -2. - */ -public class MinStack { - private int min = Integer.MAX_VALUE; - private Stack stack; - public static void main(String[] args) { - MinStack minStack = new MinStack(); - minStack.push(-2); - minStack.push(0); - minStack.push(-3); - System.out.println(minStack.getMin()); - minStack.pop(); - System.out.println(minStack.getMin()); - } - - public MinStack() { - stack = new Stack<>(); - } - - public void push(int x) { - if (min >= x) { - stack.push(min); - min = x; - } - stack.push(x); - } - - public void pop() { - if (stack.pop() == min) { - min = stack.pop(); - } - } - - public int top() { - return stack.peek(); - } - - public int getMin() { - return min; - } - -} diff --git a/LeetCode/src/normal/MissingNumber.java b/LeetCode/src/normal/MissingNumber.java deleted file mode 100644 index 7a6174af..00000000 --- a/LeetCode/src/normal/MissingNumber.java +++ /dev/null @@ -1,36 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 268.缺失数字 - * @author: mf - * @create: 2019/11/04 17:59 - */ - -/** - * 题目:https://leetcode-cn.com/problems/missing-number/ - * 难度:easy - * 类型:数组 - */ - -/* -输入: [3,0,1] -输出: 2 - -输入: [9,6,4,2,3,5,7,0,1] -输出: 8 - */ -public class MissingNumber { - public static void main(String[] args) { - int[] nums = {3, 0 , 1}; - int[] nums1 = {9,6,4,2,3,5,7,0,1}; - System.out.println(missingNumber(nums1)); - } - - private static int missingNumber(int[] nums) { - int ans = nums.length; - for (int i = 0; i < nums.length; i++) { - ans ^= nums[i]; - ans ^= i; - } - return ans; - } -} diff --git a/LeetCode/src/normal/MoveZeroes.java b/LeetCode/src/normal/MoveZeroes.java deleted file mode 100644 index 5b6d2c25..00000000 --- a/LeetCode/src/normal/MoveZeroes.java +++ /dev/null @@ -1,38 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 283.移动零 - * @author: mf - * @create: 2019/11/04 18:29 - */ - -import java.util.Arrays; - -/** - * 题目:https://leetcode-cn.com/problems/move-zeroes/ - * 难度:easy - * 类型:数组 - */ - -/* -输入: [0,1,0,3,12] -输出: [1,3,12,0,0] - */ -public class MoveZeroes { - public static void main(String[] args) { - int[] nums = {0,1,0,3,12}; - moveZeroes(nums); - System.out.println(Arrays.toString(nums)); - } - - private static void moveZeroes(int[] nums) { - int index = 0; - for (int i = 0; i < nums.length; i++) { - if (nums[i] != 0) { - nums[index++] = nums[i]; - } - } - for (int i = index; i < nums.length; i++) { - nums[i] = 0; - } - } -} diff --git a/LeetCode/src/normal/MyQueue.java b/LeetCode/src/normal/MyQueue.java deleted file mode 100644 index 4e132b7c..00000000 --- a/LeetCode/src/normal/MyQueue.java +++ /dev/null @@ -1,68 +0,0 @@ -package normal; - -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 232.用栈实现队列 - * @author: mf - * @create: 2019/11/07 14:55 - */ - -/* -题目:https://leetcode-cn.com/problems/implement-queue-using-stacks/ -类型:栈 -难度:easy - */ -/* -MyQueue queue = new MyQueue(); - -queue.push(1); -queue.push(2); -queue.peek(); // 返回 1 -queue.pop(); // 返回 1 -queue.empty(); // 返回 false - */ -public class MyQueue { - private Stack in; - private Stack out; - public static void main(String[] args) { - MyQueue myQueue = new MyQueue(); - myQueue.push(1); - myQueue.push(2); - System.out.println(myQueue.peek()); - System.out.println(myQueue.pop()); - System.out.println(myQueue.empty()); - } - - public MyQueue() { - in = new Stack<>(); - out = new Stack<>(); - } - - public void push (int x) { - in.push(x); - } - - public int pop () { - if (out.isEmpty()) { - while (! in.isEmpty()) { - out.push(in.pop()); - } - } - return out.pop(); - } - - public int peek () { - if (out.isEmpty()) { - while (! in.empty()) { - out.push(in.pop()); - } - } - return out.peek(); - } - - public boolean empty() { - return in.isEmpty() && out.isEmpty(); - } -} diff --git a/LeetCode/src/normal/MySqrt.java b/LeetCode/src/normal/MySqrt.java deleted file mode 100644 index 2ab2b041..00000000 --- a/LeetCode/src/normal/MySqrt.java +++ /dev/null @@ -1,38 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 69. x 的平方根 - * @author: mf - * @create: 2019/11/09 09:38 - */ - -/* -题目:https://leetcode-cn.com/problems/sqrtx/ -难度:easy - */ -/* -输入: 4 -输出: 2 -输入: 8 -输出: 2 -说明: 8 的平方根是 2.82842..., - 由于返回类型是整数,小数部分将被舍去。 - */ -public class MySqrt { - public static void main(String[] args) { - System.out.println(mySqrt(8)); - } - public static int mySqrt(int x) { - if (x <= 1) return x; - int l = 1, h = x; - while (l <= h) { - int mid = l + (h - l) / 2; - int sqrt = x / mid; - if (sqrt == mid ) return sqrt; - else if (mid > sqrt) h = mid - 1; - else l = mid + 1; - } - return h; - } -} diff --git a/LeetCode/src/normal/MyStack.java b/LeetCode/src/normal/MyStack.java deleted file mode 100644 index c1c9da5a..00000000 --- a/LeetCode/src/normal/MyStack.java +++ /dev/null @@ -1,52 +0,0 @@ -package normal; - -import java.util.LinkedList; -import java.util.Queue; - -/** - * @program JavaBooks - * @description: 225.用队列实现栈 - * @author: mf - * @create: 2019/11/07 15:10 - */ - -/* -题目:https://leetcode-cn.com/problems/implement-stack-using-queues/ -类型:队列 -难度:easy - */ -public class MyStack { - public static void main(String[] args) { - MyStack myStack = new MyStack(); - myStack.push(1); - myStack.push(2); - System.out.println(myStack.pop()); - } - private Queue queue; -// private LinkedList queue; - public MyStack() { - queue = new LinkedList<>(); - - } - - public void push(int x) { - queue.add(x); - int cnt = queue.size(); - while (cnt-- > 1) { - queue.add(queue.poll()); - } -// queue.addFirst(x); - } - - public int pop() { - return queue.remove(); - } - - public int top() { - return queue.peek(); - } - - public boolean empty() { - return queue.isEmpty(); - } -} diff --git a/LeetCode/src/normal/NextGreatestLetter.java b/LeetCode/src/normal/NextGreatestLetter.java deleted file mode 100644 index 2a70d295..00000000 --- a/LeetCode/src/normal/NextGreatestLetter.java +++ /dev/null @@ -1,45 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 744. 寻找比目标字母大的最小字母 - * @author: mf - * @create: 2019/11/09 10:10 - */ - -/* -题目:https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/ -难度:easy - */ -/* -输入: -letters = ["c", "f", "j"] -target = "a" -输出: "c" - -输入: -letters = ["c", "f", "j"] -target = "c" -输出: "f" - */ -public class NextGreatestLetter { - public static void main(String[] args) { - char[] letters = {'c', 'f', 'j'}; - char target = 'a'; - System.out.println(nextGreatestLetter(letters, target)); - } - - private static char nextGreatestLetter(char[] letters, char target) { - int n = letters.length; - int l = 0, h = n - 1; - while (l <= h) { - int m = l + (h - l) / 2; - if (letters[m] <= target) { - l = m + 1; - } else { - h = m - 1; - } - } - return l < n ? letters[l] : letters[0]; - } -} diff --git a/LeetCode/src/normal/PlusOne.java b/LeetCode/src/normal/PlusOne.java deleted file mode 100644 index f27364d0..00000000 --- a/LeetCode/src/normal/PlusOne.java +++ /dev/null @@ -1,42 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 加一 - * @author: mf - * @create: 2019/11/03 17:41 - */ - -import java.util.Arrays; - -/** - * 题目:https://leetcode-cn.com/problems/plus-one/ - * 难度:easy - * 类型:数组 - */ -public class PlusOne { - public static void main(String[] args) { - int[] arr = {4,3,2,1}; - int[] res = plusOne(arr); - System.out.println(Arrays.toString(res)); - } - - private static int[] plusOne(int[] digits) { - int length = digits.length; - int[] res = new int[length + 1]; - int carry = 1; // 进位 - for (int i = length - 1; i >= 0 ; i--) { - int sums = digits[i] + carry; - if (sums >= 10) { - carry = 1; - res[i] = sums % 10; - } else { - carry = 0; - res[i] = sums; - } - } - if (carry == 1) { - res[0] = 1; - return res; - } - return Arrays.copyOfRange(res,0,length); - } -} diff --git a/LeetCode/src/normal/RemoveDuplicates.java b/LeetCode/src/normal/RemoveDuplicates.java deleted file mode 100644 index 5c82798a..00000000 --- a/LeetCode/src/normal/RemoveDuplicates.java +++ /dev/null @@ -1,38 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 26.删除排序数组中的重复项 - * @author: mf - * @create: 2019/11/03 17:10 - */ - -/** - * 题目:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ - * 难度:easy - * 类型:数组 - */ - -import java.util.Arrays; - -/** - * 题目: - * 给定数组 nums = [1,1,2], - * 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 - * 你不需要考虑数组中超出新长度后面的元素。 - */ -public class RemoveDuplicates { - public static void main(String[] args) { - int[] arr = {0, 0, 1, 1, 1, 2, 2, 3, 3, 4}; - System.out.println(removeDuplicates(arr)); - System.out.println(Arrays.toString(arr)); - } - - private static int removeDuplicates(int[] arr) { - int p = 0; // 符合条件指针 - for (int i = 1; i < arr.length; i++) { - if (arr[p] != arr[i]) { - arr[++p] = arr[i]; - } - } - return p + 1; - } -} diff --git a/LeetCode/src/normal/Reverse.java b/LeetCode/src/normal/Reverse.java deleted file mode 100644 index b515ec15..00000000 --- a/LeetCode/src/normal/Reverse.java +++ /dev/null @@ -1,33 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 7. 整数反转 - * @author: mf - * @create: 2019/11/10 14:49 - */ - -/* -题目:https://leetcode-cn.com/problems/reverse-integer/ -难度:easy - */ -/* -输入: -123 -输出: -321 - */ -public class Reverse { - public static void main(String[] args) { - System.out.println(reverse(-123)); - } - public static int reverse(int x) { - long ans = 0; - while (x != 0) { - ans = ans * 10 + x % 10; - x /= 10; - } - if (ans > Integer.MAX_VALUE || ans < Integer.MIN_VALUE) { - return 0; - } - return (int) ans; - } -} diff --git a/LeetCode/src/normal/ReverseBits.java b/LeetCode/src/normal/ReverseBits.java deleted file mode 100644 index 55d5132e..00000000 --- a/LeetCode/src/normal/ReverseBits.java +++ /dev/null @@ -1,25 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 190. 颠倒二进制位 - * @author: mf - * @create: 2019/11/09 11:08 - */ - -/* -题目:https://leetcode-cn.com/problems/reverse-bits/ -难度:easy - */ -public class ReverseBits { - public static void main(String[] args) { - System.out.println(reverseBits(43261596)); - } - private static int reverseBits(int n) { - int a = 0; - for (int i = 0; i < 32; i++) { - a += ((1 & (n >> i)) << (32 - 1- i)); - } - return a; - } -} diff --git a/LeetCode/src/normal/ReverseList.java b/LeetCode/src/normal/ReverseList.java deleted file mode 100644 index cae3a056..00000000 --- a/LeetCode/src/normal/ReverseList.java +++ /dev/null @@ -1,55 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 206.反转链表 - * @author: mf - * @create: 2019/11/05 19:36 - */ - -/* -题目:https://leetcode-cn.com/problems/reverse-linked-list/comments/ -类型:链表 -难度:easy - */ - -public class ReverseList { - public static void main(String[] args) { - int[] arr = {1,2,3,4,5}; - ListNode head = ListNode.setListNode(arr); - ListNode l = reverseList2(head); - ListNode.printNode(l); - } - - /** - * 递归优点绕,还是用循环好理解一些 - * @param head - * @return - */ - private static ListNode reverseList(ListNode head) { - ListNode pre = null; // 当前节点的前一个节点 - ListNode cur = head; // 当前节点 - while (cur != null) { - ListNode nextTemp = cur.next; // 先保存当前节点的下一个节点 - cur.next = pre; // 将当前节点的下个节点指向前一个节点pre - pre = cur; // 将pre往后移动指向当前节点 - cur = nextTemp; // 将当前指点指针往后移动next - } - return pre; - } - - /** - * 尾递归 - * @param head - * @return - */ - private static ListNode reverseList2(ListNode head) { - return reverse(null, head); - } - private static ListNode reverse(ListNode pre, ListNode cur) { - if (cur == null) return pre; - ListNode next = cur.next; // 保存当前节点的下个节点的指针 - cur.next = pre; - return reverse(cur, next); - } -} diff --git a/LeetCode/src/normal/ReverseString.java b/LeetCode/src/normal/ReverseString.java deleted file mode 100644 index d12421c3..00000000 --- a/LeetCode/src/normal/ReverseString.java +++ /dev/null @@ -1,37 +0,0 @@ -package normal; - -import java.util.Arrays; - -/** - * @program JavaBooks - * @description: 344. 反转字符串 - * @author: mf - * @create: 2019/11/10 09:59 - */ - -/* -题目:https://leetcode-cn.com/problems/reverse-string/ -类型:双指针 -难度:easy - */ -public class ReverseString { - public static void main(String[] args) { - char[] s = {'h','e','l','l','o'}; - System.out.println(Arrays.toString(s)); - reverseString(s); - System.out.println(Arrays.toString(s)); - } - - private static void reverseString(char[] s) { - int p1 = 0, p2 = s.length - 1; - while (p1 < p2) { - swap(s, p1++, p2--); - } - } - - private static void swap(char[] arr, int i, int j) { - char temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} diff --git a/LeetCode/src/normal/Rob.java b/LeetCode/src/normal/Rob.java deleted file mode 100644 index a44df0e9..00000000 --- a/LeetCode/src/normal/Rob.java +++ /dev/null @@ -1,41 +0,0 @@ -package normal; - -import com.sun.org.apache.bcel.internal.generic.RET; - -/** - * @program JavaBooks - * @description: 198. 打家劫舍 - * @author: mf - * @create: 2019/11/10 13:27 - */ - -/* -题目:https://leetcode-cn.com/problems/house-robber/ -类型:动态规划 -难度:easy - */ - -/* -输入: [1,2,3,1] -输出: 4 -解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 -  偷窃到的最高金额 = 1 + 3 = 4 。 - */ -public class Rob { - public static void main(String[] args) { - int[] nums = {1,2,3,1}; - System.out.println(rob(nums)); - } - public static int rob(int[] nums) { - if (nums.length == 0) return 0; - if (nums.length == 1) return nums[0]; - int pre3 = 0, pre2 = 0, pre1 = 0; - for (int i = 0; i < nums.length; i++) { - int cur = Math.max(pre2, pre3) + nums[i]; - pre3 = pre2; - pre2 = pre1; - pre1 = cur; - } - return Math.max(pre1, pre2); - } -} diff --git a/LeetCode/src/normal/RomanToInt.java b/LeetCode/src/normal/RomanToInt.java deleted file mode 100644 index c7223d50..00000000 --- a/LeetCode/src/normal/RomanToInt.java +++ /dev/null @@ -1,51 +0,0 @@ -package normal; - -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 13. 罗马数字转整数 - * @author: mf - * @create: 2019/11/10 15:18 - */ - -/* -题目:https://leetcode-cn.com/problems/roman-to-integer/ -难度:easy - */ -/* -输入: "IV" -输出: 4 -输入: "IX" -输出: 9 -输入: "MCMXCIV" -输出: 1994 -解释: M = 1000, CM = 900, XC = 90, IV = 4. - */ -public class RomanToInt { - public static void main(String[] args) { - String s = "MCMXCIV"; - System.out.println(romanToInt(s)); - } - public static int romanToInt(String s) { - HashMap map = new HashMap<>(); - map.put('I', 1); - map.put('V', 5); - map.put('X', 10); - map.put('L', 50); - map.put('C', 100); - map.put('D', 500); - map.put('M', 1000); - int ans = 0, lastValue = 0; - for (int i = s.length() - 1; i >= 0; i--) { - int value = map.get(s.charAt(i)); - if (value < lastValue) { - ans -= value; - } else { - ans += value; - } - lastValue = value; - } - return ans; - } -} diff --git a/LeetCode/src/normal/Rotate.java b/LeetCode/src/normal/Rotate.java deleted file mode 100644 index 548d3631..00000000 --- a/LeetCode/src/normal/Rotate.java +++ /dev/null @@ -1,65 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 189.旋转数组 - * @author: mf - * @create: 2019/11/04 15:46 - */ - -import java.util.Arrays; - -/** - * 题目:https://leetcode-cn.com/problems/rotate-array/ - * 难度:easy - */ - -/* -输入: [1,2,3,4,5,6,7] 和 k = 3 -输出: [5,6,7,1,2,3,4] -解释: -向右旋转 1 步: [7,1,2,3,4,5,6] -向右旋转 2 步: [6,7,1,2,3,4,5] -向右旋转 3 步: [5,6,7,1,2,3,4] - - -输入: [-1,-100,3,99] 和 k = 2 -输出: [3,99,-1,-100] -解释: -向右旋转 1 步: [99,-1,-100,3] -向右旋转 2 步: [3,99,-1,-100] - */ -public class Rotate { - public static void main(String[] args) { - int[] nums = {1,2,3,4,5,6,7}; - int k = 3; - rotate2(nums, k); - System.out.println(Arrays.toString(nums)); - } - // 双重循环 时间复杂度:O(kn) 空间复杂度:O(1) - private static void rotate(int[] nums, int k) { - int n = nums.length; - k %= n; - for (int i = 0; i < k; i++) { - int temp = nums[n - 1]; - for (int j = n - 1; j > 0; j--) { - nums[j] = nums[j - 1]; - } - nums[0] = temp; - } - } - // 翻转 时间复杂度:O(n) 空间复杂度:O(1) - private static void rotate2(int[] nums, int k) { - int n = nums.length; - k %= n; - reverse(nums, 0, n - 1); - reverse(nums, 0, k - 1); - reverse(nums, k, n - 1); - - } - private static void reverse(int[] nums, int start, int end) { - while (start < end) { - int temp = nums[start]; - nums[start++] = nums[end]; - nums[end--] = temp; - } - } -} diff --git a/LeetCode/src/normal/SingleNumber.java b/LeetCode/src/normal/SingleNumber.java deleted file mode 100644 index 11202a47..00000000 --- a/LeetCode/src/normal/SingleNumber.java +++ /dev/null @@ -1,71 +0,0 @@ -package normal; - -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 136.只出现一次的数字 - * @author: mf - * @create: 2019/11/06 09:49 - */ - -/* -题目:https://leetcode-cn.com/problems/single-number/ -类型:哈希 -难度:easy - */ -/* -输入: [2,2,1] -输出: 1 - -输入: [4,1,2,1,2] -输出: 4 - */ - -/* -对于这道题,也可以异或 - */ -public class SingleNumber { - public static void main(String[] args) { - int[] arr = {2,2,1}; -// System.out.println(singleNumber(arr)); - System.out.println(singleNumber2(arr)); - } - - /** - * 哈希 - * @param nums - * @return - */ - private static int singleNumber(int[] nums) { - if (nums.length == 1) return nums[0]; - HashMap map = new HashMap<>(); - for (int num : nums) { - if (map.containsKey(num)) { - map.put(num, map.get(num) + 1); - } else { - map.put(num, 1); - } - } - for (Integer key : map.keySet()) { - if (map.get(key) == 1) { - return key; - } - } - return 0; - } - - /** - * 异或 - * @param nums - * @return - */ - private static int singleNumber2(int[] nums) { - if (nums.length == 1) return nums[0]; - int ans = nums[0]; - for (int i = 1; i < nums.length; i++) { - ans ^= nums[i]; - } - return ans; - } -} diff --git a/LeetCode/src/normal/SortedArrayToBST.java b/LeetCode/src/normal/SortedArrayToBST.java deleted file mode 100644 index 3a42757d..00000000 --- a/LeetCode/src/normal/SortedArrayToBST.java +++ /dev/null @@ -1,37 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 108. 将有序数组转换为二叉搜索树 - * @author: mf - * @create: 2019/11/08 15:34 - */ - -/* -题目: - */ -public class SortedArrayToBST { - public static void main(String[] args) { - - } - - /** - * [Java] 二叉树中序遍历的逆过程哦 - * @param nums - * @return - */ - private static TreeNode sortedArrayToBST(int[] nums) { - // 左右等分建立左右子树,中间节点作为子树根节点,递归该过程 - return nums == null ? null : buildTree(nums, 0, nums.length - 1); - } - private static TreeNode buildTree(int[] nums, int l, int r) { - if (l > r) { - return null; - } - int m = l + (r - l) / 2; - TreeNode root = new TreeNode(nums[m]); - root.left = buildTree(nums, l, m - 1); - root.right = buildTree(nums, m + 1, r); - return root; - } -} diff --git a/LeetCode/src/normal/StrStr.java b/LeetCode/src/normal/StrStr.java deleted file mode 100644 index 7500c008..00000000 --- a/LeetCode/src/normal/StrStr.java +++ /dev/null @@ -1,47 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 28. 实现 strStr() - * @author: mf - * @create: 2019/11/09 18:15 - */ - -/* -题目:https://leetcode-cn.com/problems/implement-strstr/ -难度:easy - */ - -public class StrStr { - public static void main(String[] args) { - String haystack = "aaa"; - String needle = "aaaa"; - System.out.println(strStr(haystack, needle)); - - } - public static int strStr(String haystack, String needle) { - if (needle.equals("")) return 0; - char[] sChar = haystack.toCharArray(); - char[] nChar = needle.toCharArray(); - int s1 = 0, s2 = 0; - int e1 = sChar.length - 1, e2 = nChar.length - 1; - while (s1 <= e1) { - if (sChar[s1] == nChar[s2]) { - int k = s1; - while (k <= e1 && s2 <= e2) { - if (sChar[k] == nChar[s2]) { - k++; - s2++; - } else { - s2 = 0; - break; - } - } - if (k >= e1 && s2 <= e2) return -1; - if (s2 > e2) return s1; - } - s1++; - } - return -1; - } -} diff --git a/LeetCode/src/normal/TitleToNumber.java b/LeetCode/src/normal/TitleToNumber.java deleted file mode 100644 index 75a6edad..00000000 --- a/LeetCode/src/normal/TitleToNumber.java +++ /dev/null @@ -1,52 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 171. Excel表列序号 - * @author: mf - * @create: 2019/11/10 19:27 - */ - -/* -题目:https://leetcode-cn.com/problems/excel-sheet-column-number/ -难度:easy - */ - -/* - A -> 1 - B -> 2 - C -> 3 - ... - Z -> 26 - AA -> 27 - AB -> 28 - ... - */ -// for (int i = 0; i < n; i++) { -// int value = s.charAt(i) - 'A' + 1; -// value = (int) (Math.pow(26, n - i - 1)) * value; -// ans += value; -// } -public class TitleToNumber { - public static void main(String[] args) { - String s = "AAB"; - System.out.println(titleToNumber(s)); - } - - public static int titleToNumber(String s) { - int ans = 0; - int n = s.length(); - int m = 26; - for (int i = 0; i < n; i++) { - int value = s.charAt(i) - 'A' + 1; - int k = n - i - 1; - int temp = 1; - while (k > 0) { - temp = temp * m; - k--; - } - ans = ans + temp * value; - } - return ans; - } -} diff --git a/LeetCode/src/normal/TrailingZeroes.java b/LeetCode/src/normal/TrailingZeroes.java deleted file mode 100644 index 32fd9ff2..00000000 --- a/LeetCode/src/normal/TrailingZeroes.java +++ /dev/null @@ -1,30 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 172. 阶乘后的零 - * @author: mf - * @create: 2019/11/10 20:20 - */ - -/* -题目:https://leetcode-cn.com/problems/factorial-trailing-zeroes/comments/ -难度:easy - */ - - - -public class TrailingZeroes { - public static void main(String[] args) { - System.out.println(trailingZeroes(10)); - } - - public static int trailingZeroes(int n) { - int count = 0; - while (n >= 5) { - count += n / 5; - n /= 5; - } - return count; - } -} diff --git a/LeetCode/src/normal/TreeNode.java b/LeetCode/src/normal/TreeNode.java deleted file mode 100644 index 7574b1dc..00000000 --- a/LeetCode/src/normal/TreeNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 二叉树 - * @author: mf - * @create: 2019/09/06 10:05 - */ - -public class TreeNode { - int val; - TreeNode left; - TreeNode right; - - public TreeNode(int val) { - this.val = val; - } - - // 给定一个前序和中序数组,生成一颗二叉树 // 根据T7笔试题 - public static TreeNode setBinaryTree(int[] pre, int[] in) { - TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); - return root; - } - - private static TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) { - - if (startPre > endPre || startIn > endIn) { - return null; - } - TreeNode root = new TreeNode(pre[startPre]); - for (int i = startIn; i <= endIn; i++) { - if (in[i] == pre[startPre]) { - root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1); - root.right = reConstructBinaryTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn); - } - } - return root; - } - - // 递归打印前序 - public static void preOrderRe(TreeNode root) { - System.out.println(root.val); - TreeNode leftNode = root.left; - if (leftNode != null) { - preOrderRe(leftNode); - } - - TreeNode rightNode = root.right; - if (rightNode != null) { - preOrderRe(rightNode); - } - } - // 递归打印中序 - public static void midOrderRe(TreeNode node) { - if (node == null) { - return; - } else { - midOrderRe(node.left); - System.out.println(node.val); - midOrderRe(node.right); - } - } - - // 递归打印后序 - public static void postOrderRe(TreeNode node) { - if (node == null) { - return; - } else { - postOrderRe(node.left); - postOrderRe(node.right); - System.out.println(node.val); - } - } - -} diff --git a/LeetCode/src/normal/TwoSum.java b/LeetCode/src/normal/TwoSum.java deleted file mode 100644 index 8476be5b..00000000 --- a/LeetCode/src/normal/TwoSum.java +++ /dev/null @@ -1,50 +0,0 @@ -package normal; /** - * @program JavaBooks - * @description: 两数之和 - * @author: mf - * @create: 2019/10/16 23:47 - */ - -import java.util.Arrays; -import java.util.HashMap; - -/** - * 给定一个整数数组 nums 和一个目标值 target, - * 请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 - * 给定 nums = [2, 7, 11, 15], - * target = 9 - * 因为 nums[0] + nums[1] = 2 + 7 = 9 - * 所以返回 [0, 1] - */ - -/** - * 方法 - * 1。 暴力法 - * 2。 两遍哈希 - * 3。 一遍哈希。 - */ - -// 时间复杂度:O(n) -// 空间复杂度:O(n) -public class TwoSum { - public static void main(String[] args) { - int[] arr = {2, 7, 11, 15}; - int target = 9; - int[] res = twoSum(arr, target); - System.out.println(Arrays.toString(res)); - } - - // 一遍哈希 - private static int[] twoSum(int[] nums, int target) { - HashMap map = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - int error = target - nums[i]; - if (map.containsKey(error)) { - return new int[] {map.get(error), i}; - } else { - map.put(nums[i], i); - } - } - return new int[0]; - } -} diff --git a/LeetCode/src/normal/isPalindromeNode.java b/LeetCode/src/normal/isPalindromeNode.java deleted file mode 100644 index 9ecc0f5a..00000000 --- a/LeetCode/src/normal/isPalindromeNode.java +++ /dev/null @@ -1,72 +0,0 @@ -package normal; - -/** - * @program JavaBooks - * @description: 234.回文链表 - * @author: mf - * @create: 2019/11/05 21:17 - */ - -/* -题目:https://leetcode-cn.com/problems/palindrome-linked-list/ -类型:链表 -难度:easy - */ -/* -输入: 1->2 -输出: false -输入: 1->2->2->1 -输出: true - */ -public class isPalindromeNode { - public static void main(String[] args) { - int[] arr = {1,2,2,1}; - ListNode head = ListNode.setListNode(arr); - System.out.println(isPalindrome(head)); - } - - private static boolean isPalindrome(ListNode head) { - if (head == null || head.next == null) return true; - // 找中点 - ListNode slow = head, fast = head.next; - while (fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - } - if (fast != null) slow = slow.next; // 说明是偶数,让slow指向下一个节点 - // 切成两半 - cut(head, slow); - - //反转比较 - return isEqual(head, reverse(slow)); - } - - private static void cut(ListNode head, ListNode cutNode) { - ListNode node = head; - while (node.next != cutNode) { - node = node.next; - } - node.next = null; - } - - private static ListNode reverse(ListNode head) { - ListNode pre = null; - ListNode cur = head; - while (cur != null) { - ListNode nextNode = cur.next; - cur.next = pre; - pre = cur; - cur = nextNode; - } - return pre; - } - - private static boolean isEqual(ListNode l1, ListNode l2) { - while (l1 != null && l2 != null) { - if (l1.val != l2.val) return false; - l1 = l1.next; - l2 = l2.next; - } - return true; - } -} diff --git a/LeetCode/src/subject/bit/T0.java b/LeetCode/src/subject/bit/T0.java deleted file mode 100644 index d794da40..00000000 --- a/LeetCode/src/subject/bit/T0.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @program JavaBooks - * @description: 多数元素 - * @author: mf - * @create: 2020/04/11 17:49 - */ - -package subject.bit; - -import java.util.HashMap; -import java.util.Map; - -/** - * 输入: [3,2,3] - * 输出: 3 - * 输入: [2,2,1,1,1,2,2] - * 输出: 2 - */ -public class T0 { - public int majorityElement(int[] nums) { - Map map = new HashMap<>((nums.length + 1) / 2); - for (int num : nums) { - if (map.containsKey(num)) { - map.put(num, map.get(num) + 1); - } else { - map.put(num, 1); - } - if (nums.length / 2 < map.get(num)) { - return num; - } - } - return 0; - } -} diff --git a/LeetCode/src/subject/bit/T1.java b/LeetCode/src/subject/bit/T1.java deleted file mode 100644 index 80ccfc26..00000000 --- a/LeetCode/src/subject/bit/T1.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @program JavaBooks - * @description: 汉明距离 - * @author: mf - * @create: 2020/04/11 18:02 - */ - -package subject.bit; - -public class T1 { - public int hammingDistance(int x, int y) { - int xor = x ^ y; - int distance = 0; - while (xor != 0) { - if (xor % 2 == 1) { - distance += 1; - } - xor = xor >> 1; - } - return distance; - } -} diff --git a/LeetCode/src/subject/bit/T2.java b/LeetCode/src/subject/bit/T2.java deleted file mode 100644 index f9227c42..00000000 --- a/LeetCode/src/subject/bit/T2.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @program JavaBooks - * @description: 只出现一次的数字 - * @author: mf - * @create: 2020/04/11 18:05 - */ - -package subject.bit; - -/** - * 输入: [2,2,1] - * 输出: 1 - * 输入: [4,1,2,1,2] - * 输出: 4 - */ -public class T2 { - public int singleNumber(int[] nums) { - if (nums.length == 1) return nums[0]; - int ans = nums[0]; - for (int i = 1; i < nums.length; i++) { - ans ^= nums[i]; - } - return ans; - } -} diff --git a/LeetCode/src/subject/dp/T0.java b/LeetCode/src/subject/dp/T0.java deleted file mode 100644 index f1cbe04f..00000000 --- a/LeetCode/src/subject/dp/T0.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @program JavaBooks - * @description: 最大子序和 - * @author: mf - * @create: 2020/04/11 19:26 - */ - -package subject.dp; - -/** - * 输入: [-2,1,-3,4,-1,2,1,-5,4], - * 输出: 6 - * 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 - */ -public class T0 { - public int maxSubArray(int[] nums) { - if (nums == null || nums.length == 0) return 0; - int max = nums[0]; - int ans = nums[0]; - for (int i = 1; i < nums.length; i++) { - max = Math.max(max + nums[i], nums[i]); - ans = Math.max(max, ans); - } - return ans; - } -} diff --git a/LeetCode/src/subject/dp/T1.java b/LeetCode/src/subject/dp/T1.java deleted file mode 100644 index 7c042430..00000000 --- a/LeetCode/src/subject/dp/T1.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @program JavaBooks - * @description: 爬楼梯 - * @author: mf - * @create: 2020/04/12 21:14 - */ - -package subject.dp; - -/** - * 输入: 2 - * 输出: 2 - * 解释: 有两种方法可以爬到楼顶。 - * 1. 1 阶 + 1 阶 - * 2. 2 阶 - */ -public class T1 { - public int climbStairs(int n) { - if (n <= 2) return n; - int pre2 = 1, pre1 = 2; - for (int i = 3; i <= n; i++) { - int cur = pre2 + pre1; - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} diff --git a/LeetCode/src/subject/dp/T2.java b/LeetCode/src/subject/dp/T2.java deleted file mode 100644 index afd6e11a..00000000 --- a/LeetCode/src/subject/dp/T2.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @program JavaBooks - * @description: 买卖股票的最佳时机 - * @author: mf - * @create: 2020/04/14 15:24 - */ - -package subject.dp; - -/** - * 输入: [7,1,5,3,6,4] - * 输出: 5 - * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 - * 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 - * 输入: [7,6,4,3,1] - * 输出: 0 - * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 - */ -public class T2 { - public int maxProfit(int[] prices) { - if (prices.length <= 1) return 0; - int min = prices[0], max = 0; - for (int i = 1; i < prices.length; i++) { - max = Math.max(max, prices[i] - min); - min = Math.min(min, prices[i]); - } - return max; - } -} diff --git a/LeetCode/src/subject/dp/T3.java b/LeetCode/src/subject/dp/T3.java deleted file mode 100644 index 260903dd..00000000 --- a/LeetCode/src/subject/dp/T3.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @program JavaBooks - * @description: 打家劫舍 - * @author: mf - * @create: 2020/04/14 15:29 - */ - -package subject.dp; - -/** - * 输入: [1,2,3,1] - * 输出: 4 - * 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 - *   偷窃到的最高金额 = 1 + 3 = 4 。 - * 输入: [2,7,9,3,1] - * 输出: 12 - * 解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 - *   偷窃到的最高金额 = 2 + 9 + 1 = 12 。 - */ -public class T3 { - public int rob(int[] nums) { - if (nums.length == 0) return 0; - if (nums.length == 1) return nums[0]; - int pre3 = 0, pre2 = 0, pre1 = 0; - for (int i = 0; i < nums.length; i++) { - int cur = Math.max(pre3, pre2) + nums[i]; - pre3 = pre2; - pre2 = pre1; - pre1 = cur; - } - return Math.max(pre2, pre1); - } -} diff --git a/LeetCode/src/subject/dp/T4.java b/LeetCode/src/subject/dp/T4.java deleted file mode 100644 index ff697503..00000000 --- a/LeetCode/src/subject/dp/T4.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @program JavaBooks - * @description: 零钱兑换 - * @author: mf - * @create: 2020/04/15 17:35 - */ - -package subject.dp; - -/** - * 输入: coins = [1, 2, 5], amount = 11 - * 输出: 3 - * 解释: 11 = 5 + 5 + 1 - * 输入: coins = [2], amount = 3 - * 输出: -1 - */ -public class T4 { - public int coinChange(int[] coins, int amount) { - // 初始化bp - int[] dp = new int[amount + 1]; - for (int i = 0; i < amount; i++) { - dp[i] = -1; - } - dp[0] = 0; // 金额0的最优解 - for (int i = 1; i <= amount; i++) { - for (int j = 0; j < coins.length; j++) { - if (i - coins[j] >= 0 && dp[i - coins[j]] != -1) { - if (dp[i] == -1 || dp[i] > dp[i - coins[j]] + 1) { - dp[i] = dp[i - coins[j]] + 1; - } - } - } - } - return dp[amount]; - } -} diff --git a/LeetCode/src/subject/dp/T5.java b/LeetCode/src/subject/dp/T5.java deleted file mode 100644 index 76e75112..00000000 --- a/LeetCode/src/subject/dp/T5.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @program JavaBooks - * @description: 三角形最小路径和 - * @author: mf - * @create: 2020/04/15 19:04 - */ - -package subject.dp; - -import java.util.List; - -/** - * [ - * [2], - * [3,4], - * [6,5,7], - * [4,1,8,3] - * ] - */ -public class T5 { - public int minimumTotal(List> triangle){ - if (triangle.size() == 0) return 0; - int row = triangle.size(); - int[][] dp = new int[row][triangle.get(row - 1).size()]; - for (int i = 0; i < row; i++) { - for (int j = 0; j < triangle.get(i).size(); j++) { - dp[i][j] = 0; - } - } - for (int i = 0; i < triangle.get(row - 1).size(); i++) { - dp[row - 1][i] = triangle.get(row - 1).get(i); - } - for (int i = row - 2; i >= 0; i--) { - for (int j = 0; j < triangle.get(i).size(); j++) { - dp[i][j] = Math.min(dp[i+1][j], dp[i+1][j+1]) + triangle.get(i).get(j); - } - } - return dp[0][0]; - } -} diff --git a/LeetCode/src/subject/dp/T6.java b/LeetCode/src/subject/dp/T6.java deleted file mode 100644 index 8f794bdf..00000000 --- a/LeetCode/src/subject/dp/T6.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: 最长上升子序列 - * @author: mf - * @create: 2020/04/17 22:14 - */ - -package subject.dp; - -/** - * 输入: [10,9,2,5,3,7,101,18] - * 输出: 4 - * 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 - */ -public class T6 { - public int lengthOfLIS(int[] nums) { - if (nums.length == 0) return 0; - int[] dp = new int[nums.length]; - dp[0] = 1; - int lis = 1; - for (int i = 1; i < dp.length; i++) { - dp[i] = 1; - for (int j = 0; j < i; j++) { - if (nums[i] > nums[j] && dp[i] < dp[j] + 1) { - dp[i] = dp[j] + 1; - } - } - lis = Math.max(lis, dp[i]); - } - return lis; - } -} diff --git a/LeetCode/src/subject/dp/T7.java b/LeetCode/src/subject/dp/T7.java deleted file mode 100644 index 0a2069dd..00000000 --- a/LeetCode/src/subject/dp/T7.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @program JavaBooks - * @description: 最小路径和 - * @author: mf - * @create: 2020/04/18 01:08 - */ - -package subject.dp; - -/** - * 输入: - * [ - * [1,3,1], - * [1,5,1], - * [4,2,1] - * ] - * 输出: 7 - * 解释: 因为路径 1→3→1→1→1 的总和最小。 - */ -public class T7 { - public int minPathSum(int[][] grid) { - if (grid.length == 0) return 0; - int row = grid.length; - int col = grid[0].length; - int[][] dp = new int[row][col]; - dp[0][0] = grid[0][0]; - for (int i = 1; i < col; i++) { - dp[0][i] = dp[0][i-1] + grid[0][i]; - } - for (int i = 1; i < row; i++) { - dp[i][0] = dp[i-1][0] + grid[i][0]; - for (int j = 1; j < col; j++) { - dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j]; - } - } - return dp[row-1][col-1]; - } -} diff --git a/LeetCode/src/subject/dp/T8.java b/LeetCode/src/subject/dp/T8.java deleted file mode 100644 index 88d0637b..00000000 --- a/LeetCode/src/subject/dp/T8.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @program JavaBooks - * @description: 最大乘积子序列 - * @author: mf - * @create: 2020/04/18 10:32 - */ - -package subject.dp; - -import java.util.Arrays; - -/** - * - */ -public class T8 { - public int maxProduct(int[] nums) { - if(nums==null || nums.length==0) return 0; - //返回值,用来存放阶段性最大值 - int res = Integer.MIN_VALUE; - //创建两个数组,分别存放阶段性最大值和最小值的累乘 - int[] dpMax = new int[nums.length+1]; - int[] dpMin = new int[nums.length+1]; - dpMax[0] = 1; - dpMin[0] = 1; - //dp的循环 - for(int i=1; i<=nums.length; i++){ - //如果当前的nums[i-1]小于0,那么 - if(nums[i-1]<0){ - int temp = dpMax[i-1]; - dpMax[i-1] = dpMin[i-1]; - dpMin[i-1] = temp; - } - dpMax[i] = Math.max(dpMax[i-1]*nums[i-1], nums[i-1]); - dpMin[i] = Math.min(dpMin[i-1]*nums[i-1], nums[i-1]); - res = Math.max(res, dpMax[i]); - } - System.out.println(Arrays.toString(nums)); - System.out.println(Arrays.toString(dpMax)); - System.out.println(Arrays.toString(dpMin)); - return res; - } - - public static void main(String[] args) { - T8 t8 = new T8(); - System.out.println(t8.maxProduct(new int[]{0, 2})); - System.out.println(t8.maxProduct(new int[]{-2, 3, -4})); - } -} diff --git a/LeetCode/src/subject/dp/T9.java b/LeetCode/src/subject/dp/T9.java deleted file mode 100644 index 0e6621ae..00000000 --- a/LeetCode/src/subject/dp/T9.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @program JavaBooks - * @description: 完全平方数 - * @author: mf - * @create: 2020/04/18 13:38 - */ - -package subject.dp; - -import java.util.Arrays; - -public class T9 { - public int numSquares(int n) { - int[] dp = new int[n+1]; - Arrays.fill(dp, Integer.MAX_VALUE); - dp[0] = 0; - int maxSquareIndex = (int) (Math.sqrt(n) + 1); - int[] squareNum = new int[maxSquareIndex]; - for (int i = 1; i < maxSquareIndex; i++) { - squareNum[i] = i * i; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j < maxSquareIndex; j++) { - if (i < squareNum[j]) break; - dp[i] = Math.min(dp[i], dp[i-squareNum[j]] + 1); - } - } - return dp[n]; - } -} diff --git a/LeetCode/src/subject/dpointer/ListNode.java b/LeetCode/src/subject/dpointer/ListNode.java deleted file mode 100644 index 1b349e60..00000000 --- a/LeetCode/src/subject/dpointer/ListNode.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @program JavaBooks - * @description: ListNode - * @author: mf - * @create: 2020/03/06 18:39 - */ - -package subject.dpointer; - -public class ListNode { - int val; - ListNode next = null; - - public ListNode(int val) { - this.val = val; - } -} diff --git a/LeetCode/src/subject/dpointer/T1.java b/LeetCode/src/subject/dpointer/T1.java deleted file mode 100644 index 34624820..00000000 --- a/LeetCode/src/subject/dpointer/T1.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @program JavaBooks - * @description: 删除排序数组中的重复项 - * @author: mf - * @create: 2020/03/13 12:05 - */ - -package subject.dpointer; - -/** - * 给定数组 nums = [1,1,2], - * 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 - */ -public class T1 { - public int removeDuplicates(int[] nums) { - int p = 0; - for (int i = 1; i < nums.length; i++) { - if (nums[p] != nums[i]) { - nums[++p] = nums[i]; - } - } - return p + 1; - } -} diff --git a/LeetCode/src/subject/dpointer/T10.java b/LeetCode/src/subject/dpointer/T10.java deleted file mode 100644 index 4342b25e..00000000 --- a/LeetCode/src/subject/dpointer/T10.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @program JavaBooks - * @description: 两个数组的交集 II - * @author: mf - * @create: 2020/04/09 17:55 - */ - -package subject.dpointer; - -import java.util.ArrayList; - -/** - * 输入: nums1 = [1,2,2,1], nums2 = [2,2] - * 输出: [2,2] - * 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] - * 输出: [4,9] - */ -public class T10 { - // 这道题跟双指针没啥关系 - public int[] intersect(int[] nums1, int[] nums2) { - ArrayList list1 = new ArrayList<>(); - for (int i : nums1) { - list1.add(i); - } - ArrayList list2 = new ArrayList<>(); - for (int i : nums2) { - if (list1.contains(i)) { - list2.add(i); - list1.remove(i); - } - } - int[] ans = new int[list2.size()]; - for (int i = 0; i < list2.size(); i++) { - ans[i] = list2.get(i); - } - return ans; - } -} diff --git a/LeetCode/src/subject/dpointer/T2.java b/LeetCode/src/subject/dpointer/T2.java deleted file mode 100644 index 6ccf709c..00000000 --- a/LeetCode/src/subject/dpointer/T2.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @program JavaBooks - * @description: 移除元素 - * @author: mf - * @create: 2020/03/22 14:40 - */ - -package subject.dpointer; - -/** - * 给定 nums = [3,2,2,3], val = 3, - * 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 - */ -public class T2 { - public int removeElement(int[] nums, int val) { - int p = 0; - for (int i = 0; i < nums.length; i++) { - if (nums[i] != val) { - nums[p++] = nums[i]; - } - } - return p; - } -} diff --git a/LeetCode/src/subject/dpointer/T3.java b/LeetCode/src/subject/dpointer/T3.java deleted file mode 100644 index 859a9ea3..00000000 --- a/LeetCode/src/subject/dpointer/T3.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @program JavaBooks - * @description: 实现strStr() - * @author: mf - * @create: 2020/03/22 14:56 - */ - -package subject.dpointer; - -public class T3 { - public int strStr(String haystack, String needle) { - int l = haystack.length(), n = needle.length(); - for (int i = 0; i < l - n + 1; i++) { - if (haystack.substring(i, i + n).equals(needle)) { - return i; - } - } - return -1; - } -} diff --git a/LeetCode/src/subject/dpointer/T4.java b/LeetCode/src/subject/dpointer/T4.java deleted file mode 100644 index 7446aa90..00000000 --- a/LeetCode/src/subject/dpointer/T4.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: 合并两个有序数组 - * @author: mf - * @create: 2020/03/22 15:13 - */ - -package subject.dpointer; - -/** - * nums1 = [1,2,3,0,0,0], m = 3 - * nums2 = [2,5,6], n = 3 - * 输出: [1,2,2,3,5,6] - */ -public class T4 { - public void merge(int[] nums1, int m, int[] nums2, int n) { - if (m == 0) { - for (int i = 0; i < n; i++) { - nums1[i] = nums2[i]; - } - } - // 双指针 - int p = nums1.length - 1; - int a1 = m - 1; - for (int i = n - 1; i >= 0; i--) { - while (a1 > 0 && nums1[a1] > nums2[i]) { - nums1[p--] = nums1[a1--]; - } - nums1[p--] = nums2[i]; - } - } -} diff --git a/LeetCode/src/subject/dpointer/T5.java b/LeetCode/src/subject/dpointer/T5.java deleted file mode 100644 index 9a174937..00000000 --- a/LeetCode/src/subject/dpointer/T5.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @program JavaBooks - * @description: 验证回文串 - * @author: mf - * @create: 2020/04/09 17:13 - */ - -package subject.dpointer; - -/** - * 输入: "A man, a plan, a canal: Panama" - * 输出: true - * 输入: "race a car" - * 输出: false - */ -public class T5 { - public boolean isPalindrome(String s) { - if (s.equals("")) return true; - s.toLowerCase(); - char[] chars = s.toCharArray(); - int l = 0, r = chars.length - 1; - while (l <= r) { - if (chars[l] == chars[r]) { - l++; - r--; - }else if (!((chars[l] >= 'a' && chars[l] <= 'z') || (chars[l] >= '0' && chars[l] >= '9'))) { - l++; - } else if (!((chars[r] >= 'a' && chars[r] <= 'z') || (chars[r] >= '0' && chars[r] >= '9'))) { - r--; - } else { - return false; - } - } - return true; - } -} diff --git a/LeetCode/src/subject/dpointer/T6.java b/LeetCode/src/subject/dpointer/T6.java deleted file mode 100644 index d5246fbd..00000000 --- a/LeetCode/src/subject/dpointer/T6.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @program JavaBooks - * @description: 环形链表 - * @author: mf - * @create: 2020/04/09 17:24 - */ - -package subject.dpointer; - -public class T6 { - public boolean hasCycle(ListNode head) { - if (head != null && head.next != null) { - // 快慢指针 - ListNode quick = head; - ListNode slow = head; - while (2 > 1) { - quick = quick.next; - if (quick == null) return false; - quick = quick.next; - if (quick == null) return false; - slow = slow.next; - if (slow == quick) return true; - } - } else { - return false; - } - } -} diff --git a/LeetCode/src/subject/dpointer/T7.java b/LeetCode/src/subject/dpointer/T7.java deleted file mode 100644 index e0c95ea6..00000000 --- a/LeetCode/src/subject/dpointer/T7.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @program JavaBooks - * @description: 两数之和 II - 输入有序数组 - * @author: mf - * @create: 2020/04/09 17:37 - */ - -package subject.dpointer; - -/** - * 输入: numbers = [2, 7, 11, 15], target = 9 - * 输出: [1,2] - * 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 - * - */ -public class T7 { - public int[] twoSum(int[] numbers, int target) { - // 双指针 - int l = 0, r = numbers.length - 1; - while (l < r) { - if (numbers[l] + numbers[r] == target) return new int[] {l+1, r+1}; - else if (numbers[l] + numbers[r] > target) r--; - else l++; - } - return new int[] {0, 0}; - } -} diff --git a/LeetCode/src/subject/dpointer/T8.java b/LeetCode/src/subject/dpointer/T8.java deleted file mode 100644 index 261c224d..00000000 --- a/LeetCode/src/subject/dpointer/T8.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @program JavaBooks - * @description: 移动零 - * @author: mf - * @create: 2020/04/09 17:40 - */ - -package subject.dpointer; - -/** - * 输入: [0,1,0,3,12] - * 输出: [1,3,12,0,0] - */ -public class T8 { - public void moveZeroes(int[] nums) { - // index记录什么开始填充0 - int index = 0; - for (int i = 0; i < nums.length; i++) { - if (nums[i] != 0) nums[index++] = nums[i]; - } - for (int i = index; i < nums.length; i++) { - nums[i] = 0; - } - } -} diff --git a/LeetCode/src/subject/dpointer/T9.java b/LeetCode/src/subject/dpointer/T9.java deleted file mode 100644 index c91f3171..00000000 --- a/LeetCode/src/subject/dpointer/T9.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @program JavaBooks - * @description: 反转字符串 - * @author: mf - * @create: 2020/04/09 17:45 - */ - -package subject.dpointer; - -/** - * 输入:["h","e","l","l","o"] - * 输出:["o","l","l","e","h"] - */ -public class T9 { - public void reverseString(char[] s) { - int l = 0, r = s.length - 1; - while (l < r) { - swap(s, l++, r--); - } - } - - public void swap(char[] s, int i, int j) { - char temp = s[i]; - s[i] = s[j]; - s[j] = temp; - } -} diff --git a/LeetCode/src/subject/hash/T0.java b/LeetCode/src/subject/hash/T0.java deleted file mode 100644 index 3b6ca947..00000000 --- a/LeetCode/src/subject/hash/T0.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @program JavaBooks - * @description: 同构字符串 - * @author: mf - * @create: 2020/04/10 19:59 - */ - -package subject.hash; - -import java.util.HashMap; - -/** - * 输入: s = "egg", t = "add" - * 输出: true - * 输入: s = "foo", t = "bar" - * 输出: false - * 输入: s = "paper", t = "title" - * 输出: true - */ -public class T0 { - public boolean isIsomorphic(String s, String t) { - return isomerphic(s, t) && isomerphic(t, s); - } - - private boolean isomerphic(String s, String t) { - int n = s.length(); - HashMap map = new HashMap<>(); - for (int i = 0; i < n; i++) { - Character c1 = s.charAt(i); - Character c2 = t.charAt(i); - if (map.containsKey(c1)) { - if (map.get(c1) != c2) { - return false; - } - } else { - map.put(c1, c2); - } - } - return true; - } -} diff --git a/LeetCode/src/subject/hash/T1.java b/LeetCode/src/subject/hash/T1.java deleted file mode 100644 index 1536dc5a..00000000 --- a/LeetCode/src/subject/hash/T1.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: 两数之和 - * @author: mf - * @create: 2020/04/10 20:11 - */ - -package subject.hash; - -import java.util.HashMap; - -/** - * 给定 nums = [2, 7, 11, 15], target = 9 - * - * 因为 nums[0] + nums[1] = 2 + 7 = 9 - * 所以返回 [0, 1] - */ -public class T1 { - public int[] twoSum(int[] nums, int target) { - if (nums == null || nums.length == 0) return null; - HashMap map = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - int error = target - nums[i]; - if (map.containsKey(error)) { - return new int[] {map.get(error), i}; - } else { - map.put(nums[i], i); - } - } - return null; - } -} diff --git a/LeetCode/src/subject/hash/T2.java b/LeetCode/src/subject/hash/T2.java deleted file mode 100644 index dd1dcadf..00000000 --- a/LeetCode/src/subject/hash/T2.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @program JavaBooks - * @description: 只出现一次的数字 - * @author: mf - * @create: 2020/04/10 20:34 - */ - -package subject.hash; - -import java.util.HashMap; -import java.util.Map; - -/** - * 输入: [2,2,1] - * 输出: 1 - * 输入: [4,1,2,1,2] - * 输出: 4 - */ -public class T2 { - /** - * 亦或 - * @param nums - * @return - */ - public int singleNumber(int[] nums) { - if (nums.length == 1) return nums[0]; - int ans = nums[0]; - for (int i = 1; i < ans; i++) { - ans ^= nums[i]; - } - return ans; - } - - /** - * 哈希 - * @param nums - * @return - */ - public int singleNumber2(int[] nums) { - if (nums.length == 1) return nums[0]; - HashMap map = new HashMap<>(); - for (int num : nums) { - if (map.containsKey(num)) { - map.put(num, map.get(num) + 1); - } else { - map.put(num, 1); - } - } - for (Map.Entry entry : map.entrySet()) { - if (entry.getKey() == 1) { - return entry.getKey(); - } - } - return -1; - } -} diff --git a/LeetCode/src/subject/hash/T3.java b/LeetCode/src/subject/hash/T3.java deleted file mode 100644 index 2c0ac2fb..00000000 --- a/LeetCode/src/subject/hash/T3.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @program JavaBooks - * @description: 存在重复元素 - * @author: mf - * @create: 2020/04/10 21:25 - */ - -package subject.hash; - -import java.util.HashSet; - -/** - * 输入: [1,2,3,1] - * 输出: true - * 输入: [1,2,3,4] - * 输出: false - * 输入: [1,1,1,3,3,4,3,2,4,2] - * 输出: true - */ -public class T3 { - public boolean containsDuplicate(int[] nums) { - HashSet set = new HashSet<>(); - for (int num : nums) { - if (!set.add(num)) { - return false; - } - } - return true; - } -} diff --git a/LeetCode/src/subject/hash/T4.java b/LeetCode/src/subject/hash/T4.java deleted file mode 100644 index a27a18e8..00000000 --- a/LeetCode/src/subject/hash/T4.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @program JavaBooks - * @description: 存在重复元素 II - * @author: mf - * @create: 2020/04/10 21:54 - */ - -package subject.hash; - -import java.util.HashSet; - -/** - * 输入: nums = [1,2,3,1], k = 3 - * 输出: true - * 输入: nums = [1,0,1,1], k = 1 - * 输出: true - * 输入: nums = [1,2,3,1,2,3], k = 2 - * 输出: false - */ -public class T4 { - public boolean containsNearbyDuplicate(int[] nums, int k) { - HashSet set = new HashSet<>(); - for (int i = 0; i < nums.length; i++) { - if (set.contains(nums[i])) return true; - set.add(nums[i]); - // 维护这个滑动窗口欧 - if (set.size() > k) { - set.remove(nums[i - k]); - } - } - return false; - } -} diff --git a/LeetCode/src/subject/hash/T5.java b/LeetCode/src/subject/hash/T5.java deleted file mode 100644 index 1309dc98..00000000 --- a/LeetCode/src/subject/hash/T5.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @program JavaBooks - * @description: 单词规律 - * @author: mf - * @create: 2020/04/10 22:12 - */ - -package subject.hash; - -import java.util.HashMap; - -/** - * 输入: pattern = "abba", str = "dog cat cat dog" - * 输出: true - * 输入:pattern = "abba", str = "dog cat cat fish" - * 输出: false - * 输入: pattern = "aaaa", str = "dog cat cat dog" - * 输出: false - * 输入: pattern = "abba", str = "dog dog dog dog" - * 输出: false - */ -public class T5 { - public boolean wordPattern(String pattern, String str) { - String[] array1 = str.split(" "); - if (array1.length != pattern.length()) return false; - String[] array2 = pattern.split(""); - return wordPatternHelper(array1, array2) && wordPatternHelper(array2, array1); - } - - private boolean wordPatternHelper(String[] array1, String[] array2) { - HashMap map = new HashMap<>(); - for (int i = 0; i < array1.length; i++) { - String key = array1[i]; - if (map.containsKey(key)) { - if (!map.get(key).equals(array1[i])) { - return false; - } - } else { - map.put(key, array2[i]); - } - } - return true; - } -} diff --git a/LeetCode/src/subject/linked/ListNode.java b/LeetCode/src/subject/linked/ListNode.java deleted file mode 100644 index fa1ef0f0..00000000 --- a/LeetCode/src/subject/linked/ListNode.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @program JavaBooks - * @description: ListNode - * @author: mf - * @create: 2020/03/06 18:39 - */ - -package subject.linked; - -public class ListNode { - int val; - ListNode next = null; - - public ListNode(int val) { - this.val = val; - } -} diff --git a/LeetCode/src/subject/linked/Node.java b/LeetCode/src/subject/linked/Node.java deleted file mode 100644 index 8b00c443..00000000 --- a/LeetCode/src/subject/linked/Node.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @program JavaBooks - * @description: Node - * @author: mf - * @create: 2020/03/08 02:20 - */ - -package subject.linked; - -public class Node { - int val; - Node next; - Node random; - - public Node(int val) { - this.val = val; - this.next = null; - this.random = null; - } -} diff --git a/LeetCode/src/subject/linked/T1.java b/LeetCode/src/subject/linked/T1.java deleted file mode 100644 index cc3d93f1..00000000 --- a/LeetCode/src/subject/linked/T1.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @program JavaBooks - * @description: 从尾到头打印链表 - * @author: mf - * @create: 2020/03/06 18:40 - */ - -package subject.linked; - -import java.util.ArrayList; -import java.util.Stack; - -/** - * 原先: - * 1->2->3->4 - * 之后: - * 1<-2<-3<-4 - */ -public class T1 { - ArrayList list = new ArrayList(); - /** - * 递归 - * @param listNode - * @return - */ - public ArrayList printListFromTailToHead(ListNode listNode) { - if (listNode != null) { - this.printListFromTailToHead(listNode.next); - list.add(listNode.val); - } - return list; - } - - /** - * 栈试试 - * @param listNode - * @return - */ - public ArrayList printListFromTailToHead2(ListNode listNode) { - Stack stack = new Stack<>(); - while (listNode != null) { - stack.push(listNode.val); - listNode = listNode.next; - } - while (!stack.empty()) { - list.add(stack.pop()); - } - return list; - } -} diff --git a/LeetCode/src/subject/linked/T2.java b/LeetCode/src/subject/linked/T2.java deleted file mode 100644 index f2a68ce1..00000000 --- a/LeetCode/src/subject/linked/T2.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @program JavaBooks - * @description: 链表中倒数第k个节点 - * @author: mf - * @create: 2020/03/06 20:55 - */ - -package subject.linked; - -import java.util.Stack; - -/** - * 1->2->3->4->5 k = 2 - * 返回: - * 4->5 - */ -public class T2 { - /** - * 栈,比较容易想得的到 - * @param head - * @param k - * @return - */ - public ListNode getKthFromEnd(ListNode head, int k) { - Stack stack = new Stack<>(); - while (head != null) { - stack.push(head); - head = head.next; - } - ListNode listNode= new ListNode(0); - for (int i = 0; i < k; i++) { - listNode = stack.pop(); - } - return listNode; - } - - /** - * 双指针 - * @param head - * @param k - * @return - */ - public ListNode getKthFromEnd2(ListNode head, int k) { - ListNode pNode = head; - ListNode kNode = head; - int p1 = 0; - while (pNode != null) { - if (p1 >= k) { - kNode = kNode.next; - } - pNode = pNode.next; - p1++; - } - return kNode; - } -} diff --git a/LeetCode/src/subject/linked/T3.java b/LeetCode/src/subject/linked/T3.java deleted file mode 100644 index acbaed40..00000000 --- a/LeetCode/src/subject/linked/T3.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @program JavaBooks - * @description: 反转链表 - * @author: mf - * @create: 2020/03/07 12:04 - */ - -package subject.linked; - -/** - * 1->2->3->4->5->NULL - * NULL<-1<-2<-3<-4<-5 - */ -public class T3 { - /** - * 迭代 - * 流程: - * 核心还是双指针... - * pre和cur一直移动 - * 接着相互指向 - * @param head - * @return - */ - public ListNode reverseList(ListNode head) { - ListNode pre = null; // 当前节点之前的节点 null - ListNode cur = head; - while (cur != null) { - ListNode nextTemp = cur.next; // 获取当前节点的下一个节点 - cur.next = pre; // 当前节点的下个节点指向前一个节点 - // 尾递归其实省了下面这两步 - pre = cur; // 将前一个节点指针移动到当前指针 - cur = nextTemp; // 当当前节点移动到下一个节点 - } - return pre; - } - - /** - * 递归:尾递归 - * @param head - * @return - */ - public ListNode reverseList2(ListNode head) { - return reverse(null, head); - } - - public ListNode reverse(ListNode pre, ListNode cur) { - if (cur == null) return pre; // 如果当前节点为null,直接返回 - ListNode next = cur.next; // next节点指向当前节点的下一个节点 - cur.next = pre; // 将当前节点指向 当前节点的前一个节点 - return reverse(cur, next); - } -} diff --git a/LeetCode/src/subject/linked/T4.java b/LeetCode/src/subject/linked/T4.java deleted file mode 100644 index 9d781f3c..00000000 --- a/LeetCode/src/subject/linked/T4.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @program JavaBooks - * @description: 合并两个有序链表 - * @author: mf - * @create: 2020/03/07 15:13 - */ - -package subject.linked; - -/** - * 输入:1->2->4, 1->3->4 - * 输出:1->1->2->3->4->4 - */ -public class T4 { - /** - * 递归 - * 比较大小,谁小,谁的下一个节点指向递归的结果 - * @param l1 - * @param l2 - * @return - */ - public ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; - if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1, l2.next); - return l2; - } - } -} diff --git a/LeetCode/src/subject/linked/T5.java b/LeetCode/src/subject/linked/T5.java deleted file mode 100644 index 90511b09..00000000 --- a/LeetCode/src/subject/linked/T5.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @program JavaBooks - * @description: 复杂链表的复制 - * @author: mf - * @create: 2020/03/08 02:19 - */ - -package subject.linked; - -/** - * 输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] - * 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] - */ -public class T5 { - /** - * 迭代三次 - * @param head - * @return - */ - public Node copyRandomList(Node head) { - if (head == null) return null; - Node node = head; // 一般这样的操作其实就是好比是指针了,链表定义个指针, 走你 - // 第一次迭代的目的是复制next - while (node != null) { - // 接下来的三步操作 复制节点 - Node temp = new Node(node.val); // new一个和node值相同的当前节点 temp 比如1` - temp.next = node.next; // temp 的下个节点指向 node的下个节点, 比如1`>2 - node.next = temp; // node 的下个节点 指向temp 比如 1 > 1` > 2 - // 一般迭代, 都会有这一步操作, 移动指针 - node = temp.next; // 将node 指针 指向 temp的下个节点, 比如2 - } - // 这次的目的让复制的节点的random 和 原先的random各个指向的一致 - // 将指针移动首部 - node = head; - while (node != null) { - // 2 2` - // ^_^ ^_^ - // 1 > 1` > 2 > 2` > 3 > 3` - node.next.random = node.random == null ? null : node.random.next; - // 迭代,移动指针 - node = node.next.next; - } - // 第三次目的是切断 返回复制的链表 - // 双指针, 重新指向 - node = head; - Node pCloneHead = head.next; - while (node != null) { - Node temp = node.next; // 其实就是当前的复制节点 - node.next = temp.next; // 其实就是 1 > 2 - temp.next = temp.next == null ? null : temp.next.next; // 其实就是 1` > 2` - // 迭代, 移动指针 - node = node.next; - } - return pCloneHead; - } -} diff --git a/LeetCode/src/subject/linked/T6.java b/LeetCode/src/subject/linked/T6.java deleted file mode 100644 index 27f633dc..00000000 --- a/LeetCode/src/subject/linked/T6.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @program JavaBooks - * @description: 两个链表的第一个公共节点 - * @author: mf - * @create: 2020/03/08 20:41 - */ - -package subject.linked; - -import java.util.HashSet; - -/** - * - */ -public class T6 { - /** - * 最容易想的是哈希 - * headA走完一圈 - * 开始走headB,判断哪个节点和A相等,即可 - * @param headA - * @param headB - * @return - */ - public ListNode getIntersectionNode(ListNode headA, ListNode headB) { - if (headA == null || headB == null) return null; - HashSet set = new HashSet<>(); - while (headA != null) { - set.add(headA); - headA = headA.next; - } - while (headB != null) { - if (set.contains(headB)) { - return headB; - } - headB = headB.next; - } - return null; - } - - /** - * 双指针 - * @param headA - * @param headB - * @return - */ - public ListNode getIntersectionNode2(ListNode headA, ListNode headB) { - if (headA == null || headB == null) return null; - // 一男一女 - ListNode node1 = headA; - ListNode node2 = headB; - // 我走你,你走我,直到相爱 - while (node1 != node2) { - node1 = node1 == null ? headB : node1.next; - node2 = node2 == null ? headA : node2.next; - } - return node1; - } -} diff --git a/LeetCode/src/subject/linked/T7.java b/LeetCode/src/subject/linked/T7.java deleted file mode 100644 index 6bdcf0e1..00000000 --- a/LeetCode/src/subject/linked/T7.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @program JavaBooks - * @description: 删除链表的节点 - * @author: mf - * @create: 2020/03/08 21:11 - */ - -package subject.linked; - -/** - * head = [4,5,1,9], node = 5 - * 输出: [4,1,9] - */ -public class T7 { - /** - * node的下个节点强行替代他 - * @param node - */ - public void deleteNode(ListNode node) { - // node 5 node.next 1 - node.val = node.next.val; - node.next = node.next.next; - } -} diff --git a/LeetCode/src/subject/sq/T1.java b/LeetCode/src/subject/sq/T1.java deleted file mode 100644 index 55d6830e..00000000 --- a/LeetCode/src/subject/sq/T1.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @program JavaBooks - * @description: 用两个栈实现队列 - * @author: mf - * @create: 2020/03/11 12:12 - */ - -package subject.sq; - -import java.util.Stack; - -public class T1 { - private Stack in; - private Stack out; - /** Initialize your data structure here. */ - public T1() { - in = new Stack<>(); - out = new Stack<>(); - } - - /** Push element x to the back of queue. */ - public void push(int x) { - in.push(x); // 输入栈,不断的push元素,没啥特殊的 - } - - /** Removes the element from in front of queue and returns that element. */ - public int pop() { - // 弹出栈在弹出前,先判断栈是否有元素,如果为空就从in栈中导入到out栈 - if(out.isEmpty()) { - while(! in.isEmpty()) { - out.push(in.pop()); - } - } - return out.pop(); - } - - /** Get the front element. */ - public int peek() { - if(out.isEmpty()) { - while(! in.isEmpty()) { - out.push(in.pop()); - } - } - return out.peek(); - } - - /** Returns whether the queue is empty. */ - public boolean empty() { - return in.isEmpty() && out.isEmpty(); - } -} diff --git a/LeetCode/src/subject/sq/T2.java b/LeetCode/src/subject/sq/T2.java deleted file mode 100644 index aab309e8..00000000 --- a/LeetCode/src/subject/sq/T2.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @program JavaBooks - * @description: 用一个队列实现栈 - * @author: mf - * @create: 2020/03/11 12:24 - */ - -package subject.sq; - -import java.util.LinkedList; -import java.util.Queue; - -public class T2 { - private Queue queue; - - public T2() { - queue = new LinkedList<>(); - } - - /** Push element x onto stack. */ - public void push(int x) { - queue.add(x); - // 加下来的操作是将头部的数据重新加入到队列后边 - // 比如, 1,2 经过以下操作:2,1 - // 比如,2,1,3 经过以下操作:3,2,1 - // 实际上,就是进来一个元素,就把他放到队头 - int cnt = queue.size(); - while(cnt > 1) { - cnt--; - queue.add(queue.poll()); - } - } - - /** Removes the element on top of the stack and returns that element. */ - public int pop() { - return queue.remove(); - } - - /** Returns whether the stack is empty. */ - public boolean empty() { - return queue.isEmpty(); - } - /** Get the top element. */ - public int top() { - return queue.peek(); - } - - public static void main(String[] args) { - T2 t2 = new T2(); - t2.push(1); - t2.push(2); - t2.push(3); - System.out.println(t2.pop()); - } -} diff --git a/LeetCode/src/subject/sq/T3.java b/LeetCode/src/subject/sq/T3.java deleted file mode 100644 index 806e77e9..00000000 --- a/LeetCode/src/subject/sq/T3.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @program JavaBooks - * @description: 两个队列实现栈 - * @author: mf - * @create: 2020/03/11 13:07 - */ - -package subject.sq; - -import java.util.LinkedList; -import java.util.Queue; - -public class T3 { - private Queue q1 = new LinkedList<>(); - private Queue q2 = new LinkedList<>(); - private int top; - - public void push(int x) { - // 添加顺序1, 2 - q2.add(x); // 2 - top = x; - while (!q1.isEmpty()) { - q2.add(q1.remove()); //2, 1 - } - Queue temp = q1; // q1 null - q1 = q2; - q2 = temp; - } - - public int pop() { - int res = top; - q1.remove(); - if (!q1.isEmpty()) { - top = q1.peek(); - } - return res; - } -} diff --git a/LeetCode/src/subject/sq/T4.java b/LeetCode/src/subject/sq/T4.java deleted file mode 100644 index 712696a7..00000000 --- a/LeetCode/src/subject/sq/T4.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @program JavaBooks - * @description: 最小栈(两个栈实现) - * @author: mf - * @create: 2020/03/12 11:53 - */ - -package subject.sq; - -import java.util.Stack; - -public class T4 { - private Stack dataStack = new Stack<>(); - private Stack minStack = new Stack<>(); - private Integer min = Integer.MAX_VALUE; - - public void push(int x) { - dataStack.push(x); - min = Math.min(x, min); - minStack.push(min); - } - - public void pop() { - dataStack.pop(); - minStack.pop(); - min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peek(); - } - - public int top() { - return dataStack.peek(); - } - - public int getMin() { - return minStack.peek(); - } - -} diff --git a/LeetCode/src/subject/sq/T5.java b/LeetCode/src/subject/sq/T5.java deleted file mode 100644 index f6a01a6a..00000000 --- a/LeetCode/src/subject/sq/T5.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @program JavaBooks - * @description: 最小栈 - * @author: mf - * @create: 2020/03/12 14:44 - */ - -package subject.sq; - -import java.util.Stack; - -public class T5 { - private Integer min = Integer.MAX_VALUE; - private Stack stack = new Stack<>(); - - public void push(int x) { - if (x <= min) { - stack.push(min); - min = x; - } - stack.push(x); - } - - public void pop() { - if (stack.pop() == min) { - min = stack.pop(); - } - } - - public int top() { - return stack.peek(); - } - - public int getMin() { - return min; - } - -} diff --git a/LeetCode/src/subject/sq/T6.java b/LeetCode/src/subject/sq/T6.java deleted file mode 100644 index a817edb3..00000000 --- a/LeetCode/src/subject/sq/T6.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: 有效的括号 - * @author: mf - * @create: 2020/03/12 15:21 - */ - -package subject.sq; - -import java.util.Stack; - -public class T6 { - public boolean isValid(String s) { - Stack stack = new Stack<>(); - for (char c : s.toCharArray()) { - if (stack.size() == 0) { - stack.push(c); - } else if (isSym(stack.peek(), c)) { - stack.pop(); - } else { - stack.push(c); - } - } - return stack.size() == 0; - } - - public boolean isSym(char c1, char c2) { - return (c1 == '(' && c2 == ')') - || (c1 == '[' && c2 == ']') - || (c1 == '{' && c2 == '}'); - } -} diff --git a/LeetCode/src/subject/tree/T1.java b/LeetCode/src/subject/tree/T1.java deleted file mode 100644 index 45e384af..00000000 --- a/LeetCode/src/subject/tree/T1.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @program JavaBooks - * @description: 相同的树 - * @author: mf - * @create: 2020/03/09 13:14 - */ - -package subject.tree; - -/** - * 输入: 1 1 - * / \ / \ - * 2 3 2 3 - * - * [1,2,3], [1,2,3] - * 输出 true - */ -public class T1 { - /** - * 递归判断(实际上前行遍历) - * @param p - * @param q - * @return - */ - public boolean isSameTree(TreeNode p, TreeNode q) { - if (p == null && q == null) return true; // 递归到最后二者都是null 说明相同 - if (p == null || q == null) return false; // 递归到其中有一方为空,则不相同 - if (p.val != q.val) return false; // 值不相等 - return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); - } -} diff --git a/LeetCode/src/subject/tree/T2.java b/LeetCode/src/subject/tree/T2.java deleted file mode 100644 index 498c7083..00000000 --- a/LeetCode/src/subject/tree/T2.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @program JavaBooks - * @description: 对称二叉树 - * @author: mf - * @create: 2020/03/09 13:28 - */ - -package subject.tree; - -/** - * 1 - * / \ - * 2 2 - * / \ / \ - * 3 4 4 3 - */ -public class T2 { - /** - * 还是前序递归 - * @param root - * @return - */ - public boolean isSymmetric(TreeNode root) { - return isSym(root, root); - } - - public boolean isSym(TreeNode root, TreeNode root1) { - if (root == null && root1 == null) return true; - if (root == null || root1 == null) return false; - if (root.val != root1.val) return false; - return isSym(root.left, root1. right) && isSym(root.right, root1.left); - } -} diff --git a/LeetCode/src/subject/tree/T3.java b/LeetCode/src/subject/tree/T3.java deleted file mode 100644 index d58dc2f8..00000000 --- a/LeetCode/src/subject/tree/T3.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @program JavaBooks - * @description: 二叉树的最大深度 - * @author: mf - * @create: 2020/03/09 13:36 - */ - -package subject.tree; - -/** - * 3 - * / \ - * 9 20 - * / \ - * 15 7 - * 返回结果是3 - */ -public class T3 { - /** - * 递归 - * @param root - * @return - */ - public int maxDepth(TreeNode root) { - return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; - } -} diff --git a/LeetCode/src/subject/tree/T4.java b/LeetCode/src/subject/tree/T4.java deleted file mode 100644 index 74c5aff3..00000000 --- a/LeetCode/src/subject/tree/T4.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @program JavaBooks - * @description: 将有序数组转成二叉搜索树 - * @author: mf - * @create: 2020/03/09 17:06 - */ - -package subject.tree; - -public class T4 { - /** - * 中序逆遍历 - * @param nums - * @return - */ - public TreeNode sortedArrayToBST(int[] nums) { - return nums == null ? null : buildTreee(nums, 0, nums.length - 1); - } - - public TreeNode buildTreee(int[] nums, int l, int r) { - if (l > r) return null; - int m = l + (r - l) / 2; - // left -> root -> right - TreeNode root = new TreeNode(nums[m]); // new一个root - root.left = buildTreee(nums, l, m - 1); - root.right = buildTreee(nums, m + 1, r); - return root; - } -} diff --git a/LeetCode/src/subject/tree/T5.java b/LeetCode/src/subject/tree/T5.java deleted file mode 100644 index 0deb3355..00000000 --- a/LeetCode/src/subject/tree/T5.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @program JavaBooks - * @description: 平衡二叉树 - * @author: mf - * @create: 2020/03/09 17:14 - */ - -package subject.tree; - -/** - * 3 - * / \ - * 9 20 - * / \ - * 15 7 - * true - */ -public class T5 { - /** - * 自顶向下递归 - * @param root - * @return - */ - public boolean isBalanced(TreeNode root) { - if (root == null) return true; - return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 - && isBalanced(root.left) && isBalanced(root.right); - } - - public int maxDepth(TreeNode root) { - if (root == null) return 0; - return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); - } -} diff --git a/LeetCode/src/subject/tree/T6.java b/LeetCode/src/subject/tree/T6.java deleted file mode 100644 index f8405f35..00000000 --- a/LeetCode/src/subject/tree/T6.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @program JavaBooks - * @description: 二叉树最小深度 - * @author: mf - * @create: 2020/03/09 21:34 - */ - -package subject.tree; - -/** - * 3 - * / \ - * 9 20 - * / \ - * 15 7 - * 2 - */ -public class T6 { - /** - * 注意仔细读题,一开始没想到根左和根右任意为空的情况 - * @param root - * @return - */ - public int minDepth(TreeNode root) { - if(root == null) return 0; - int m1 = minDepth(root.left); - int m2 = minDepth(root.right); - return root.left == null || root.right == null ? m1 + m2 + 1 : Math.min(m1, m2) + 1; - } -} diff --git a/LeetCode/src/subject/tree/T7.java b/LeetCode/src/subject/tree/T7.java deleted file mode 100644 index 8074f444..00000000 --- a/LeetCode/src/subject/tree/T7.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @program JavaBooks - * @description: 路径总和 - * @author: mf - * @create: 2020/03/10 14:14 - */ - -package subject.tree; - -/** - * 5 - * / \ - * 4 8 - * / / \ - * 11 13 4 - * / \ \ - * 7 2 1 - * sum = 22 - */ -public class T7 { - /** - * 递归 - * @param root - * @param sum - * @return - */ - public boolean hasPathSum(TreeNode root, int sum) { - if (root == null) return false; - // 总和减去路径上节点的值 - sum -= root.val; - // 如果节的左右节点都为空,说明那个路径上走完了,可以开始比较sum是否为0 - if (root.left == null && root.right == null) return sum == 0; - return hasPathSum(root.left, sum) || hasPathSum(root.right, sum); - } -} diff --git a/LeetCode/src/subject/tree/T8.java b/LeetCode/src/subject/tree/T8.java deleted file mode 100644 index a6ad19c5..00000000 --- a/LeetCode/src/subject/tree/T8.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @program JavaBooks - * @description: 翻转二叉树 - * @author: mf - * @create: 2020/03/10 14:30 - */ - -package subject.tree; - -public class T8 { - /** - * 还是递归 - * @param root - * @return - */ - public TreeNode invertTree(TreeNode root) { - if (root == null) return null; - TreeNode left = invertTree(root.left); - TreeNode right = invertTree(root.right); - root.left = right; - root.right = left; - return root; - } -} diff --git a/LeetCode/src/subject/tree/TreeNode.java b/LeetCode/src/subject/tree/TreeNode.java deleted file mode 100644 index c8d7a6a9..00000000 --- a/LeetCode/src/subject/tree/TreeNode.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @program JavaBooks - * @description: TreeNode - * @author: mf - * @create: 2020/03/09 13:13 - */ - -package subject.tree; - -public class TreeNode { - int val; - TreeNode left; - TreeNode right; - - public TreeNode(int val) { - this.val = val; - } -} diff --git "a/LeetCode/\344\270\223\351\242\230.md" "b/LeetCode/\344\270\223\351\242\230.md" deleted file mode 100644 index 4631df6a..00000000 --- "a/LeetCode/\344\270\223\351\242\230.md" +++ /dev/null @@ -1,55 +0,0 @@ -## 专题总结 -**个人感觉以下熟能生巧就可以了,毕竟...** -## 链表 -- [面试题06-从尾到头打印链表](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035) --->[题解](/src/subject/linked/T1.java) -- [面试题22-链表中倒数第k个结点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) --->[题解](/src/subject/linked/T2.java) -- [面试题24-反转链表](https://leetcode-cn.com/problems/reverse-linked-list/) --->[题解](/src/subject/linked/T3.java) -- [面试题25-合并两个排序的链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) --->[题解](/src/subject/linked/T4.java) -- [面试题35-复杂链表的复制](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/) --->[题解](./src/subject/linked/T5.java) -- [面试题52-两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) --->[题解](./src/subject/linked/T6.java) -- [面试题18-删除链表的节点](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/) --->[题解](./src/subject/linked/T7.java) - -## 树 -- 面试题07-重建二叉树 -- 面试题26-树的子结构 -- 面试题27-二叉树的镜像 -- 面试题32-1 -从上往下打印二叉树 -- 面试题33-二叉搜索树的后序遍历序列 -- 面试题34-二叉树中和为某一值的路径 -- 面试题36-二叉搜索树与双向链表 -- 面试题55-1-二叉树的深度 -- 面试题55-2-平衡二叉树 -- 面试题28-对称的二叉树 -- 面试题37-序列化二叉树 -- 面试题54-二叉搜索树的第k大节点 - -## Stack & Queue -- 面试题09-用两个栈实现队列 -- 面试题30-包含min函数的栈 -- 面试题31-栈的压入、弹出序列 -- 面试题58-1-翻转单词顺序 -- 面试题59-1-滑动窗口的最大值 - -## Heap -- 面试题40-最小的K个数 - -## 斐波那契数列 -- 面试题10-1-斐波拉契数列 -- 面试题10-2-青蛙跳台阶问题 - -## 搜索算法 -- 面试题04-二维数组中的查找 -- 面试题11-旋转数组的最小数字(二分查找) -- 面试题56-1-数组中数字出现的次数(二分查找) - -## 动态规划 - - -## 位运算 \ No newline at end of file diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\344\272\214\345\210\206\346\263\225.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\344\272\214\345\210\206\346\263\225.md" deleted file mode 100644 index 9d548912..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\344\272\214\345\210\206\346\263\225.md" +++ /dev/null @@ -1,24 +0,0 @@ -## 二分法 - -> 思想:在有序列表中,取中间记录作为比较对象,若给定值与中间记录的关键词相等,则查找成功; - 若给定值小于中间记录的关键词,则在中间记录的左半区继续查找; - 若给定值大于中间的关键字,则在中间记录的右半区继续查找。 - 不断重复上述锅中,直到查找成功。 - -```java -public class BinarySearch { - public static int binarySearch(int[] arr, int target) { - if (arr == null || arr.length == 0) return 0; - int left = 0; - int right = arr.length; - int mid; - while (left < right) { - mid = (left + right) >> 1; - if (target < arr[mid]) right = mid - 1; - else if (target > arr[mid]) left = mid + 1; - else return mid; - } - return 0; - } -} -``` \ No newline at end of file diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\344\275\215\350\277\220\347\256\227.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\344\275\215\350\277\220\347\256\227.md" deleted file mode 100644 index d63572b6..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\344\275\215\350\277\220\347\256\227.md" +++ /dev/null @@ -1,91 +0,0 @@ -## 引言 - -**线性表-位运算** - -## 位元算的相关题目 - - - -### [136. 只出现一次的数字](https://leetcode-cn.com/problems/single-number/) - -分析:异或 - -```java -class Solution { - public int singleNumber(int[] nums) { - if(nums.length == 1) return nums[0]; - int ans = nums[0]; - for(int i = 1; i < nums.length; i++) { - ans ^= nums[i]; - } - return ans; - } -} -``` - -#### [190. 颠倒二进制位](https://leetcode-cn.com/problems/reverse-bits/) - -```java -public class Solution { - // you need treat n as an unsigned value - public int reverseBits(int n) { - int a = 0; - for (int i = 0; i <= 31; i++) { - a = a + ((1 & (n >> i)) << (31 - i)); - } - return a; - } -} -``` - -### [191. 位1的个数](https://leetcode-cn.com/problems/number-of-1-bits/) - -```bash -输入:00000000000000000000000000001011 -输出:3 -解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 -``` - -```java -public class Solution { - // you need to treat n as an unsigned value - public int hammingWeight(int n) { - int ans = 0; - while(n != 0) { - n &= n - 1; - ans++; - } - return ans; - } -} -``` - -### [268. 缺失数字](https://leetcode-cn.com/problems/missing-number/) - -分析:异或 - -```java -class Solution { - public int missingNumber(int[] nums) { - int ans = nums.length; - for(int i = 0; i < nums.length; i++) { - ans ^= nums[i]; - ans ^= i; - } - return ans; - } -} -``` - -### [371. 两整数之和](https://leetcode-cn.com/problems/sum-of-two-integers/) - -分析:异或和与 - -```java -class Solution { - public int getSum(int a, int b) { - return b == 0 ? a : getSum(a^b, (a&b) << 1); - } -} -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\212\250\346\200\201\350\247\204\345\210\222.md" deleted file mode 100644 index 20c26b91..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\212\250\346\200\201\350\247\204\345\210\222.md" +++ /dev/null @@ -1,289 +0,0 @@ -## 引言 - -**线性表-动态规划** - -## 相关题目 - -### [53. 最大子序和](https://leetcode-cn.com/problems/maximum-subarray/) - - - -```bash -输入: [-2,1,-3,4,-1,2,1,-5,4], -输出: 6 -解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 -``` - -```java -class Solution { - public int maxSubArray(int[] nums) { - if(nums == null || nums.length == 0 ) return 0; - int max = nums[0]; // 记录包含arr[i]的连续子数组的和的最大值 - int ans = nums[0]; // 记录当前所有子数组的和的最大值 - for(int i = 1; i < nums.length; i++) { - max = Math.max(max + nums[i], nums[i]); - ans = Math.max(max, ans); - } - return ans; - } -} -``` - -### [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) - -```bash -输入: 2 -输出: 2 -解释: 有两种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 -2. 2 阶 -输入: 3 -输出: 3 -解释: 有三种方法可以爬到楼顶。 -1. 1 阶 + 1 阶 + 1 阶 -2. 1 阶 + 2 阶 -3. 2 阶 + 1 阶 -``` - -```java -class Solution { - public int climbStairs(int n) { - if(n <= 2) return n; - int pre2 = 1, pre1 = 2; - for(int i = 3; i <= n; i++) { - int cur = pre2 + pre1; - pre2 = pre1; - pre1 = cur; - } - return pre1; - } -} -``` - -### [121. 买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) - -```bash -输入: [7,1,5,3,6,4] -输出: 5 -解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 - 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 -``` - -```java -class Solution { - public int maxProfit(int[] prices) { - if(prices.length <= 1) return 0; - int min = prices[0], max = 0; - for(int i = 1; i < prices.length; i++){ - max = Math.max(max, prices[i] - min); - min = Math.min(min, prices[i]); - } - return max; - } -} -``` - -### [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) - -```bash -输入: [1,2,3,1] -输出: 4 -解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 -  偷窃到的最高金额 = 1 + 3 = 4 。 -输入: [2,7,9,3,1] -输出: 12 -解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 -  偷窃到的最高金额 = 2 + 9 + 1 = 12 。 -``` - -```java -class Solution { - public int rob(int[] nums) { - if(nums.length == 0) return 0; - if(nums.length == 1) return nums[0]; - int pre3 = 0, pre2 = 0, pre1 = 0; - for (int i = 0; i < nums.length; i++) { - int cur = Math.max(pre3, pre2) + nums[i]; - pre3 = pre2; - pre2 = pre1; - pre1 = cur; - } - return Math.max(pre2, pre1); - } -} -``` - -### [322.零钱兑换](https://leetcode-cn.com/problems/coin-change/) - -``` -给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 -输入: coins = [1, 2, 5], amount = 11 -输出: 3 -解释: 11 = 5 + 5 + 1 - -输入: coins = [2], amount = 3 -输出: -1 -``` - -```java -class Solution { - public int coinChange(int[] coins, int amount) { - // 初始化dp - int[] dp = new int[amount + 1]; - for (int i = 0; i <= amount; i++) { - dp[i] = -1; - } - dp[0] = 0; // 金额为0的最优解 - for (int i = 1; i <= amount; i++) { - for (int j = 0; j < coins.length; j++) { - if (i - coins[j] >= 0 && dp[i - coins[j]] != -1) { - if (dp[i] == -1 || dp[i] > dp[i - coins[j]] + 1) { - dp[i] = dp[i - coins[j]] + 1; - } - } - } - } - return dp[amount]; - } -} -``` - -### [300. 最长上升子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/) - -``` -给定一个无序的整数数组,找到其中最长上升子序列的长度。 -输入: [10,9,2,5,3,7,101,18] -输出: 4 -解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 -``` - -```java -class Solution { - public int lengthOfLIS(int[] nums) { - if (nums.length == 0) return 0; - int[] dp = new int[nums.length]; - dp[0] = 1; - int lis = 1; - for (int i = 1; i < dp.length; i++) { - dp[i] = 1; - for (int j = 0; j < i; j++) { - if (nums[i] > nums[j] && dp[i] < dp[j] + 1) { - dp[i] = dp[j] + 1; - } - } - lis = Math.max(lis, dp[i]); - } - return lis; - } -} -``` - -### [64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/) - -``` -给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 -输入: -[ - [1,3,1], - [1,5,1], - [4,2,1] -] -输出: 7 -解释: 因为路径 1→3→1→1→1 的总和最小。 -``` - -```java -class Solution { - public int minPathSum(int[][] grid) { - if (grid.length == 0) return 0; - int row = grid.length; - int col = grid[0].length; - // 定义dp - int[][] dp = new int[row][col]; - dp[0][0] = grid[0][0]; - for(int i = 1; i < col; i++) { - dp[0][i] = dp[0][i-1] + grid[0][i]; - } - //start - for (int i = 1; i < row; i++) { - dp[i][0] = dp[i-1][0] + grid[i][0]; - for (int j = 1; j < col; j++) { - dp[i][j] = Math.min(dp[i][j-1], dp[i-1][j]) + grid[i][j]; - } - } - return dp[row-1][col-1]; - } -} -``` - -### [152. 乘积最大子数组](https://leetcode-cn.com/problems/maximum-product-subarray/) - -``` -给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字)。 -输入: [2,3,-2,4] -输出: 6 -解释: 子数组 [2,3] 有最大乘积 6。 -输入: [-2,0,-1] -输出: 0 -解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 -``` - -```java -class Solution { - public int maxProduct(int[] nums) { - if (nums.length == 0) return 0; - int ans = Integer.MIN_VALUE; - int[] dpMax = new int[nums.length + 1]; - int[] dpMin = new int[nums.length + 1]; - dpMax[0] = 1; - dpMin[0] = 1; - for (int i = 1; i <= nums.length; i++) { - if (nums[i-1] < 0) { - int temp = dpMax[i-1]; - dpMax[i-1] = dpMin[i-1]; - dpMin[i-1] = temp; - } - dpMax[i] = Math.max(dpMax[i-1]*nums[i-1], nums[i-1]); - dpMin[i] = Math.min(dpMin[i-1]*nums[i-1], nums[i-1]); - ans = Math.max(ans, dpMax[i]); - } - return ans; - } -} -``` - -### [279. 完全平方数](https://leetcode-cn.com/problems/perfect-squares/) - -``` -给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 -输入: n = 12 -输出: 3 -解释: 12 = 4 + 4 + 4. - -输入: n = 13 -输出: 2 -解释: 13 = 4 + 9. -``` - -```java -class Solution { - public int numSquares(int n) { - int[] dp = new int[n+1]; - Arrays.fill(dp, Integer.MAX_VALUE); - dp[0] = 0; - int maxSIndex = (int) (Math.sqrt(n) + 1); - int[] squareNum = new int[maxSIndex]; - for (int i = 1; i < maxSIndex; i++) { - squareNum[i] = i * i; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j < maxSIndex; j++) { - if (i < squareNum[j]) break; - dp[i] = Math.min(dp[i], dp[i - squareNum[j]] + 1); - } - } - return dp[n]; - } -} -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\217\214\346\214\207\351\222\210.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\217\214\346\214\207\351\222\210.md" deleted file mode 100644 index 32be5280..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\217\214\346\214\207\351\222\210.md" +++ /dev/null @@ -1,248 +0,0 @@ -## 引言 - -**线性表-双指针** - -### [26. 删除排序数组中的重复项](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) - - - -``` -给定数组 nums = [1,1,2], - -函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 - -你不需要考虑数组中超出新长度后面的元素。 -``` - -```java -class Solution { - public int removeDuplicates(int[] nums) { - int p = 0; - for(int i = 1; i < nums.length; i++) { - if(nums[p] != nums[i]) { - nums[++p] = nums[i]; - } - } - return p+1; - } -} -``` - -### [28. 实现 strStr()](https://leetcode-cn.com/problems/implement-strstr/) - -```bash -输入: haystack = "hello", needle = "ll" -输出: 2 -输入: haystack = "aaaaa", needle = "bba" -输出: -1 -``` - -```java -class Solution { - public int strStr(String haystack, String needle) { - if (needle.equals("")) return 0; - char[] sChar = haystack.toCharArray(); - char[] nChar = needle.toCharArray(); - int s1 = 0, s2 = 0; - int e1 = sChar.length - 1, e2 = nChar.length - 1; - while (s1 <= e1) { - if (sChar[s1] == nChar[s2]) { - int k = s1; - while (k <= e1 && s2 <= e2) { - if (sChar[k] == nChar[s2]) { - k++; - s2++; - } else { - s2 = 0; - break; - } - } - if (k >= e1 && s2 <= e2) return -1; - if (s2 > e2) return s1; - } - s1++; - } - return -1; - } -} -``` - -### [88. 合并两个有序数组](https://leetcode-cn.com/problems/merge-sorted-array/) - -```bash -输入: -nums1 = [1,2,3,0,0,0], m = 3 -nums2 = [2,5,6], n = 3 - -输出: [1,2,2,3,5,6] -``` - -```java -class Solution { - public void merge(int[] nums1, int m, int[] nums2, int n) { - if(m == 0) { - for(int i = 0; i < n; i++){ - nums1[i] = nums2[i]; - } - return; - } - int p = nums1.length - 1; - int a1 = m - 1; - for(int i = n - 1; i >= 0; i--) { - while(a1 >= 0 && nums1[a1] > nums2[i]) { - nums1[p--] = nums1[a1--]; - } - nums1[p--] = nums2[i]; - } - } -} -``` - -### [125. 验证回文串](https://leetcode-cn.com/problems/valid-palindrome/) - -```bash -输入: "A man, a plan, a canal: Panama" -输出: true -输入: "race a car" -输出: false -``` - -```java -class Solution { - public boolean isPalindrome(String s) { - if (s.equals("")) return true; - s = s.toLowerCase(); - char[] sChar = s.toCharArray(); - int l = 0, r = sChar.length - 1; - while (l <= r) { - if (sChar[l] == sChar[r]) { - l++; - r--; - } else if (!((sChar[l] >= 'a' && sChar[l] <= 'z') || (sChar[l] >= '0' && sChar[l] <= '9'))) { - l++; - } else if (!((sChar[r] >= 'a' && sChar[r] <= 'z') || (sChar[r] >= '0' && sChar[r] <= '9'))) { - r--; - } else { - return false; - } - } - return true; - } -} -``` - -### [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) - -```java -public class Solution { - public boolean hasCycle(ListNode head) { - if(head != null && head.next != null) { - ListNode quick = head; - ListNode slow = head; - while(2 > 1) { - quick = quick.next; - if(quick == null) return false; - quick = quick.next; - if(quick == null) return false; - slow = slow.next; - if(slow == quick) return true; - } - } else { - return false; - } - } -} -``` - -### [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/) - -```java -class Solution { - public boolean isPalindrome(ListNode head) { - if(head == null || head.next == null) return true; - // 找中点 - ListNode slow = head, fast = head.next; - while(fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - } - if(fast != null) slow = slow.next; - // cut - cut(head, slow); - // 比较 - return isEqual(head, reverse(slow)); - - } - - public void cut (ListNode head, ListNode cutNode) { - ListNode node = head; - while(node.next != cutNode) { - node = node.next; - } - node.next = null; - } - - public ListNode reverse(ListNode head) { - ListNode pre = null; - ListNode cur = head; - while(cur != null) { - ListNode nextNode = cur.next; - cur.next = pre; - pre = cur; - cur = nextNode; - } - return pre; - } - - public boolean isEqual(ListNode l1, ListNode l2) { - while(l1 != null && l2 != null) { - if(l1.val != l2.val) return false; - l1 = l1.next; - l2 = l2.next; - } - return true; - } -} -``` - -### [283. 移动零](https://leetcode-cn.com/problems/move-zeroes/) - -```java -class Solution { - public void moveZeroes(int[] nums) { - int index = 0; - for(int i = 0; i < nums.length; i++) { - if(nums[i] != 0) { - nums[index++] = nums[i]; - } - } - for(int i = index; i < nums.length; i++) { - nums[i] = 0; - } - } -} -``` - -### [344. 反转字符串](https://leetcode-cn.com/problems/reverse-string/) - -```bash -输入:["h","e","l","l","o"] -输出:["o","l","l","e","h"] -``` - -```java -class Solution { - public void reverseString(char[] s) { - int p1 = 0, p2 = s.length - 1; - while(p1 < p2 ){ - swap(s, p1++, p2--); - } - } - public void swap(char[] s, int i, int j) { - char temp = s[i]; - s[i] = s[j]; - s[j] = temp; - } -} -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\223\210\345\270\214\350\241\250.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\223\210\345\270\214\350\241\250.md" deleted file mode 100644 index 63b6533c..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\345\223\210\345\270\214\350\241\250.md" +++ /dev/null @@ -1,295 +0,0 @@ -## 引言 - -**线性表-哈希表** - -## 相关题目 - -哈希表使用 O(N) 空间复杂度存储数据,并且以 O(1) 时间复杂度求解问题。 - - - -- Java 中的 **HashSet** 用于存储一个集合,可以查找元素是否在集合中。如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在。例如对于只有小写字符的元素,就可以用一个长度为 26 的布尔数组来存储一个字符集合,使得空间复杂度降低为 O(1)。 -- Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。 - -### [1. 两数之和](https://leetcode-cn.com/problems/two-sum/) - -```bash -给定 nums = [2, 7, 11, 15], target = 9 - -因为 nums[0] + nums[1] = 2 + 7 = 9 -所以返回 [0, 1] -``` - -分析:哈希 - -```java -class Solution { - public int[] twoSum(int[] nums, int target) { - if (nums == null || nums.length == 0) return null; - HashMap map = new HashMap<>(); - for (int i = 0; i < nums.length; i++) { - int error = target - nums[i]; - if (map.containsKey(error)) { - return new int[] {map.get(error), i}; - } else { - map.put(nums[i], i); - } - } - return new int[0]; - } -} -``` - -### [136. 只出现一次的数字](https://leetcode-cn.com/problems/single-number/) - -```bash -输入: [2,2,1] -输出: 1 -输入: [4,1,2,1,2] -输出: 4 -``` - -分析:哈希或者异或 - -```java -/** -* 哈希 -* @param nums -* @return -*/ -class Solution { - public int singleNumber(int[] nums) { - if(nums.length == 1) return nums[0]; - HashMap map = new HashMap<>(); - for(int num : nums) { - if(map.containsKey(num)) { - map.put(num, map.get(num) + 1); - } else { - map.put(num, 1); - } - } - for(Integer key : map.keySet()) { - if(map.get(key) == 1) { - return key; - } - } - return 0; - } -} -``` - -```java -/** -* 异或 -* @param nums -* @return -*/ -class Solution { - public int singleNumber(int[] nums) { - if(nums.length == 1) return nums[0]; - int ans = nums[0]; - for(int i = 1; i < nums.length; i++) { - ans ^= nums[i]; - } - return ans; - } -} -``` - -### [202. 快乐数](https://leetcode-cn.com/problems/happy-number/) - -```bash -输入: 19 -输出: true -解释: -12 + 92 = 82 -82 + 22 = 68 -62 + 82 = 100 -12 + 02 + 02 = 1 -``` - -分析:递归,哈希 - -```java -// 递归 -class Solution { - public boolean isHappy(int n) { - if (n == 1) return true; - if (n != 4) { - int sum = 0, k = n; - while (k > 0) { - sum += (k % 10) * (k % 10); - k /= 10; - } - return isHappy(sum); - } - return false; - } -} -``` - -```java -// 哈希 只要sum有重复的,必然是不快乐了。 -public boolean isHappy(int n) { - if(n == 1) return true; - HashSet set = new HashSet<>(); - while(2 > 1) { - int sum = 0; - while (n > 0) { - sum += (n % 10) *(n % 10); - n /= 10; - } - if(sum == 1) return true; - if(!set.add(sum)) return false; - n = sum; - } - } -``` - -### [217. 存在重复元素](https://leetcode-cn.com/problems/contains-duplicate/) - -```bash -输入: [1,2,3,1] -输出: true -输入: [1,2,3,4] -输出: false -输入: [1,1,1,3,3,4,3,2,4,2] -输出: true -``` - -分析:哈希 - -```java -class Solution { - public boolean containsDuplicate(int[] nums) { - HashSet set = new HashSet<>(); - for (int num : nums) { - if (!set.add(num)) { - return true; - } - } - return false; - } -} -``` - -### [242. 有效的字母异位词](https://leetcode-cn.com/problems/valid-anagram/) - -```bash -输入: s = "anagram", t = "nagaram" -输出: true -输入: s = "rat", t = "car" -输出: false -``` - -分析:字符串或者哈希 - -```java -// 哈希 -class Solution { - public boolean isAnagram(String s, String t) { - HashMap map = new HashMap<>(); - for(char c : s.toCharArray()) { - map.put(c, map.getOrDefault(c, 0) + 1); - } - for(char c : t.toCharArray()) { - Integer count = map.get(c); - if(count == null) { - return false; - } else if (count > 1) { - map.put(c, count - 1); - } else { - map.remove(c); - } - } - return map.isEmpty(); - } -} -``` - -```java -// 字符串 -class Solution { - public boolean isAnagram(String s, String t) { - int[] sCount = new int[26]; - int[] tCount = new int[26]; - for(char c : s.toCharArray()) { - sCount[c - 'a']++; - } - for(char c : t.toCharArray()) { - tCount[c - 'a']++; - } - for(int i = 0; i < 26; i++) { - if(sCount[i] != tCount[i]) { - return false; - } - } - return true; - } -} -``` - -### [350. 两个数组的交集 II](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/) - -```bash -输入: nums1 = [1,2,2,1], nums2 = [2,2] -输出: [2,2] -输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] -输出: [4,9] -``` - -分析:哈希 - -```java -class Solution { - public int[] intersect(int[] nums1, int[] nums2) { - HashMap map = new HashMap<>(); - for(int num : nums1) { - map.put(num, map.getOrDefault(num, 0) + 1); - } - ArrayList list = new ArrayList<>(); - for(int num : nums2) { - Integer count = map.get(num); - if(count != null && count != 0) { - list.add(num); - map.put(num, --count); - } - } - int ans[] = new int[list.size()]; - for(int i = 0; i < list.size(); i++) { - ans[i] = list.get(i); - } - return ans; - } -} -``` - -### [387. 字符串中的第一个唯一字符](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) - -```bash -s = "leetcode" -返回 0. - -s = "loveleetcode", -返回 2. -``` - -分:哈希 - -```java -class Solution { - public int firstUniqChar(String s) { - HashMap map = new HashMap<>(); - for (char c : s.toCharArray()){ - map.put(c, map.getOrDefault(c, 0) + 1); - } - for (int i = 0; i < s.length(); i++) { - if(map.get(s.charAt(i)) == 1) { - return i; - } - } - return -1; - } -} -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\216\222\345\272\217.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\216\222\345\272\217.md" deleted file mode 100644 index 8c4c16dd..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\216\222\345\272\217.md" +++ /dev/null @@ -1,245 +0,0 @@ -## 排序对比 -![排序对比](https://www.pdai.tech/_images/alg/alg-sort-overview-1.png) - -## 冒泡排序 -- 比较相邻的两个元素,如果第一个比第二个大,就交换他们两个。 -- 对每一对相邻的元素作同样的工作,从开始第一到结尾的最后一对。这步结束后,最后的元素会是最大的数 - -```java -public class BubbleSort { - public static void bubbleSort(int[] arr) { - for (int i = 1; i < arr.length; i++) { - for (int j = 0; j < arr.length - i; j++) { - // 如果当前的数比后面的数大,则交换 - if (arr[j] > arr[j + 1]) { - swap(arr, j, j + 1); - } - } - } - } - public static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} -``` -## 选择排序 -- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 -- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 -- 重复第二步,直到所有元素均排序完毕。 - -```java -public class SelectSort { - public static void selectSort(int[] arr) { - // N-1轮 - for (int i = 0; i < arr.length - 1; i++) { - int min = i; - // 每轮需要N-i次比较 - for (int j = i + 1; j < arr.length; j++) { - min = arr[j] < arr[min] ? j : min; // 保存最小值坐标 - } - // 将找到最小值的坐标与i交换 - swap(arr, i, min); - } - } - public static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} -``` - -## 插入排序 - -- 将第一待排序序列第一个元素对齐一个有序序列,把第二个元素到最后一个元素当成是末排序序列。 -- 从头到尾依次扫描末尾排序序列,将扫描到的每个元素插入有序序列的适当位置。 -- 如果待插入的元素与有序序列中的某个元素替代,则将待插入元素插入到替代元素的后面) - -```java -public class InsertSort { - public static void insertSort(int[] arr) { - for (int i = 1; i < arr.length; i++) { - for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { - swap(arr, j, j + 1); - } - } - } - public static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} -``` - -## 归并排序 - -- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; -- 设定两个指针,最初位置分别为两个已经排序序列的起始位置; -- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; -- 重复步骤 3 直到某一指针达到序列尾; -- 将另一序列剩下的所有元素直接复制到合并序列尾。 - -```java -public class MergeSort { - public static void mergeSort(int[] arr) { - if (arr == null || arr.length < 2) { - return; - } - - mergeSort(arr, 0, arr.length - 1); - } - - public static void mergeSort(int[] arr, int left, int right) { - if (left == right) return; - int mid = left + ((right - left) >> 1); - // left - mergeSort(arr, left, mid); - // right - mergeSort(arr, mid + 1, right); - // merge - merge(arr, left, mid, right); - } - - public static void merge(int[] arr, int left, int mid, int right) { - int[] help = new int[right - left + 1]; - int i = 0; - int p1 = left; - int p2 = mid + 1; - while (p1 <= mid && p2 <= right) { - help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; - } - while(p1 <= mid) { - help[i++] = arr[p1++]; - } - while(p2 <= right) { - help[i++] = arr[p2++]; - } - for (int j = 0; j < help.length; j++) { - arr[left + j] = help[j]; - } - } -} -``` - -## 快速排序 - -- 从数列中挑出一个元素,称为"基准"; -- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以放到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作; -- 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序; - -```java -public class QuickSort { - public static int[] quickSort(int[] arr) { - return quickSort(arr, 0, arr.length - 1); - } - - public static int[] quickSort(int[] arr, int left, int right) { - if (left < right) { - int partitionIndex = partition(arr, left, right); - // 左半部分递归 - quickSort(arr, left, partitionIndex - 1); - // 右半部分递归 - quickSort(arr, partitionIndex + 1, right); - } - return arr; - } - - public static int partition(int[] arr, int left, int right) { - int pivot = left; - int index = pivot + 1; - for (int i = index; i <= right; i++) { - if (arr[i] < arr[pivot]) { - swap(arr, i, index++); - } - } - swap(arr, pivot, index - 1); - return index - 1; - } - - public static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } - -} -``` - -## 堆排序 - -- 创建一个堆 H[0……n-1]; -- 创建一个大根堆 -- 堆顶和堆尾交换 -- 重复第三步骤 - -```java -public class HeapSort { - public static void heapSort(int[] arr) { - if (arr == null || arr.length < 2) return; - for (int i = 0; i < arr.length; i++) { - heapInsert(arr, i); // 依次从0~i形成大根堆 - } - int heapSize = arr.length; - swap(arr, 0, --heapSize); - while (heapSize > 0) { - heapify(arr, 0, heapSize); - swap(arr, 0, --heapSize); - } - } - - public static void heapInsert(int[] arr, int index) { - // 建立大根堆 - while (arr[index] > arr[(index - 1) / 2]) { - swap(arr, index, (index - 1) / 2); - index = (index - 1) / 2; - } - } - - public static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} -``` - -## 桶排序 - -```java -public class BucketSort { - public static void bucketSort(int[] arr, int bucketSize) { - if (arr.length == 0) return; - int minValue = arr[0]; - int maxValue = arr[0]; - for (int value : arr) { - if (value < minValue) minValue = value; - if (value > maxValue) maxValue = value; - } - - // 桶的数量 - int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1; - int[][] buckets = new int[bucketCount][0]; - // 映射 - for(int i = 0; i < arr.length; i++) { - int index = (int) Math.floor((arr[i] - minValue) / bucketSize); - buckets[index] = arrAppend(buckets[index], arr[i]); - } - int arrIndex = 0; - for (int[] bucket : buckets) { - if (bucket.length < 0) continue; - Arrays.sort(bucket); - for (int value : bucket) arr[arrIndex++] = value; - } - } - - private static int[] arrAppend(int[] arr, int value) { - arr = Arrays.copyOf(arr, arr.length + 1); - arr[arr.length - 1] = value; - return arr; - } -} -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\240\210\345\222\214\351\230\237\345\210\227.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\240\210\345\222\214\351\230\237\345\210\227.md" deleted file mode 100644 index bf594c66..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\240\210\345\222\214\351\230\237\345\210\227.md" +++ /dev/null @@ -1,266 +0,0 @@ -## 引言 - -**线性表-栈和队列** - -## 栈-LIFO - -实现 - -- 使用数组实现的叫`静态栈` -- 使用链表实现的叫`动态栈` - -## 队列-FIFO - -实现 - -- 使用链表实现的叫`动态栈` -- 使用链表实现的叫`动态队列` - -## 相关题目 - - - -### [20. 有效的括号](https://leetcode-cn.com/problems/valid-parentheses/) - -```bash -输入: "()" -输出: true -输入: "()[]{}" -输出: true -输入: "(]" -输出: false -``` - -分析:栈 - -```java -class Solution { - public boolean isValid(String s) { - Stack stack = new Stack<>(); - for(char c : s.toCharArray()) { - if(stack.size() == 0) { - stack.push(c); - } else if(isSym(stack.peek(), c)) { - stack.pop(); - } else { - stack.push(c); - } - } - return stack.size() == 0; - } - - public boolean isSym(char c1, char c2) { - return (c1 == '(' && c2 == ')') - || (c1 == '[' && c2 == ']') - || (c1 == '{' && c2 == '}'); - } -} -``` - -### [155. 最小栈](https://leetcode-cn.com/problems/min-stack/) - -```bash -MinStack minStack = new MinStack(); -minStack.push(-2); -minStack.push(0); -minStack.push(-3); -minStack.getMin(); --> 返回 -3. -minStack.pop(); -minStack.top(); --> 返回 0. -minStack.getMin(); --> 返回 -2. -``` - -分析:当前值比min小,一次性入两次栈,相当于记录了上次的最小值,每次弹出去的时候,也将当前最小值弹出去,且重新找回上次记录的最小值 - -也可以用辅助栈 - -```java -class MinStack { - private int min = Integer.MAX_VALUE; - private Stack stack; - /** initialize your data structure here. */ - public MinStack() { - stack = new Stack<>(); - } - - public void push(int x) { - if(x <= min) { - stack.push(min); - min = x; - } - stack.push(x); - } - - public void pop() { - if(stack.pop() == min) { - min = stack.pop(); - } - } - - public int top() { - return stack.peek(); - } - - public int getMin() { - return min; - } -} -``` - -```java -class MinStack { - - private Stack dataStack; - private Stack minStack; - private int min; - - public MinStack() { - dataStack = new Stack<>(); - minStack = new Stack<>(); - min = Integer.MAX_VALUE; - } - - public void push(int x) { - dataStack.add(x); - min = Math.min(min, x); - minStack.add(min); - } - - public void pop() { - dataStack.pop(); - minStack.pop(); - min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peek(); - } - - public int top() { - return dataStack.peek(); - } - - public int getMin() { - return minStack.peek(); - } -} -``` - -### [232. 用栈实现队列](https://leetcode-cn.com/problems/implement-queue-using-stacks/) - -```bash -MyQueue queue = new MyQueue(); -queue.push(1); -queue.push(2); -queue.peek(); // 返回 1 -queue.pop(); // 返回 1 -queue.empty(); // 返回 false -``` - -分析:两个栈 - -```java -class MyQueue { - private Stack in; - private Stack out; - /** Initialize your data structure here. */ - public MyQueue() { - in = new Stack<>(); - out = new Stack<>(); - } - - /** Push element x to the back of queue. */ - public void push(int x) { - in.push(x); - } - - /** Removes the element from in front of queue and returns that element. */ - public int pop() { - if(out.isEmpty()) { - while(! in.isEmpty()) { - out.push(in.pop()); - } - } - return out.pop(); - } - - /** Get the front element. */ - public int peek() { - if(out.isEmpty()) { - while(! in.isEmpty()) { - out.push(in.pop()); - } - } - return out.peek(); - } - - /** Returns whether the queue is empty. */ - public boolean empty() { - return in.isEmpty() && out.isEmpty(); - } -} -``` - -### [225. 用队列实现栈](https://leetcode-cn.com/problems/implement-stack-using-queues/) - -分析:在将一个元素 x 插入队列时,为了维护原来的后进先出顺序,需要让 x 插入队列首部。而队列的默认插入顺序是队列尾部,因此在将 x 插入队列尾部之后,需要让除了 x 之外的所有元素出队列,再入队列。 - -```java -class MyStack { - private Queue queue; - /** Initialize your data structure here. */ - public MyStack() { - queue = new LinkedList<>(); - } - - /** Push element x onto stack. */ - public void push(int x) { - queue.add(x); - int cnt = queue.size(); - while(cnt-- > 1) { - queue.add(queue.poll()); - } - } - - /** Removes the element on top of the stack and returns that element. */ - public int pop() { - return queue.remove(); - } - - /** Get the top element. */ - public int top() { - return queue.peek(); - } - - /** Returns whether the stack is empty. */ - public boolean empty() { - return queue.isEmpty(); - } -} -``` - -用两个队列实现栈,比如加入的顺序是1,2,3; - -```java - private Queue q1 = new LinkedList<>(); - private Queue q2 = new LinkedList<>(); - private int top; - - public void push(int x) { - // 添加顺序1, 2 - q2.add(x); // 2 - top = x; - while (!q1.isEmpty()) { - q2.add(q1.remove()); //2, 1 - } - Queue temp = q1; // q1 null - q1 = q2; - q2 = temp; - } - - public int pop() { - int res = top; - q1.remove(); - if (!q1.isEmpty()) { - top = q1.peek(); - } - return res; - } -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\240\221-\345\237\272\347\241\200.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\240\221-\345\237\272\347\241\200.md" deleted file mode 100644 index 12dcf6ae..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\346\240\221-\345\237\272\347\241\200.md" +++ /dev/null @@ -1,442 +0,0 @@ -## 引言 - -**线性表-树-基础** - - - -![](https://www.pdai.tech/_images/alg/alg-tree-0.png) - -## 树 - -树是一种数据结构,它是n(n>=0)个节点的有限集。n=0时称为空树。n>0时,有限集的元素构成一个具有层次感的数据结构。 - -![](https://www.pdai.tech/_images/alg/alg-tree-1.png) - -区别于线性表一对一的元素关系,树中的节点是一对多的关系。树具有以下特点: - -- n>0时,根节点是唯一的,不可能存在多个根节点。 -- 每个节点有零个至多个子节点;除了根节点外,每个节点有且仅有一个父节点。根节点没有父节点。 - -## 树的相关概念 - -- `子树`: 除了根节点外,每个子节点都可以分为多个不相交的子树。 -- `孩子与双亲`: 若一个结点有子树,那么该结点称为子树根的"双亲",子树的根是该结点的"孩子"。在图一中,B、H是A的孩子,A是B、H的双亲。 -- `兄弟`: 具有相同双亲的节点互为兄弟,例如B与H互为兄弟。 -- `节点的度`: 一个节点拥有子树的数目。例如A的度为2,B的度为1,C的度为3. -- `叶子`: 没有子树,也即是度为0的节点。 -- `分支节点`: 除了叶子节点之外的节点,也即是度不为0的节点。 -- `内部节点`: 除了根节点之外的分支节点。 -- `层次`: 根节点为第一层,其余节点的层次等于其双亲节点的层次加1. -- `树的高度`: 也称为树的深度,树中节点的最大层次。 -- `有序树`: 树中节点各子树之间的次序是重要的,不可以随意交换位置。 -- `无序树`: 树种节点各子树之间的次序是不重要的。可以随意交换位置。 -- `森林`: 0或多棵互不相交的树的集合。 - -## 二叉树、完全二叉树、满二叉树 - -- 二叉树: 最多有两棵子树的树被称为二叉树 - -![](https://www.pdai.tech/_images/alg/alg-tree-3.png) - -- 斜树: 所有节点都只有左子树的二叉树叫做左斜树,所有节点都只有右子树的二叉树叫做右斜树。(本质就是链表) - -![](https://www.pdai.tech/_images/alg/alg-tree-4.png) - -- 满二叉树: 二叉树中所有非叶子结点的度都是2,且叶子结点都在同一层次上 - -![](https://www.pdai.tech/_images/alg/alg-tree-5.png) - -- 完全二叉树: 如果一个二叉树与满二叉树前m个节点的结构相同,这样的二叉树被称为完全二叉树 - -![](https://www.pdai.tech/_images/alg/alg-tree-6.png) - -## 二叉查找树-BST - -二叉查找树(Binary Search Tree)是指一棵空树或者具有下列性质的二叉树: - -- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值; -- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值; -- 任意节点的左、右子树也分别为二叉查找树; -- 没有键值相等的节点。 - -二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低为 O ( log ⁡ n ) 。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、多重集、关联数组等。 - -![](https://www.pdai.tech/_images/alg/alg-tree-binary-search-1.svg) - -## 平衡二叉树-AVL - -含有相同节点的二叉查找树可以有不同的形态,而二叉查找树的平均查找长度与树的深度有关,所以需要找出一个查找平均长度最小的一棵,那就是平衡二叉树,具有以下性质: - -- 要么是棵空树,要么其根节点左右子树的深度之差的绝对值不超过1; -- 其左右子树也都是平衡二叉树; -- 二叉树节点的平衡因子定义为该节点的左子树的深度减去右子树的深度。则平衡二叉树的所有节点的平衡因子只可能是-1,0,1。 - -![](https://www.pdai.tech/_images/alg/alg-tree-balance-tree-1.jpg) - -## 红黑树 - -红黑树也是一种自平衡的二叉查找树。 - -- 每个结点要么是红的要么是黑的。(红或黑) -- 根结点是黑的。 (根黑) -- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。 (叶黑) -- 如果一个结点是红的,那么它的两个儿子都是黑的。 (红子黑) -- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。(路径下黑相同) - -![](https://www.pdai.tech/_images/alg/alg-tree-14.png) - -应用场景: - -- Java ConcurrentHashMap & TreeMap -- C++ STL: map & set -- linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块 -- epoll在内核中的实现,用红黑树管理事件块 -- nginx中,用红黑树管理timer等 - -## B-树 - -B树(英语: B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree),可以拥有最多2个子节点。与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。 - -## B+树 - -B+ 树是一种树数据结构,通常用于关系型数据库(如Mysql)和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。 - -在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。 - -b+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少。 - -将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示: - -![](https://www.pdai.tech/_images/alg/alg-tree-16.png) - -## 总结 - -我们知道,实际应用当中,我们经常使用的是`查找`和`排序`操作,这在我们的各种管理系统、数据库系统、操作系统等当中,十分常用。 - -`数组`的下标寻址十分迅速,但计算机的内存是有限的,故数组的长度也是有限的,实际应用当中的数据往往十分庞大;而且无序数组的查找最坏情况需要遍历整个数组;后来人们提出了二分查找,二分查找要求数组的构造一定有序,二分法查找解决了普通数组查找复杂度过高的问题。任和一种数组无法解决的问题就是插入、删除操作比较复杂,因此,在一个增删查改比较频繁的数据结构中,数组不会被优先考虑 - -`普通链表`由于它的结构特点被证明根本不适合进行查找 - -`哈希表`是数组和链表的折中,同时它的设计依赖散列函数的设计,数组不能无限长、链表也不适合查找,所以也适合大规模的查找 - -`二叉查找树`因为可能退化成链表,同样不适合进行查找 - -`AVL树`是为了解决可能退化成链表问题,但是AVL树的旋转过程非常麻烦,因此插入和删除很慢,也就是构建AVL树比较麻烦 - -`红黑树`是平衡二叉树和AVL树的折中,因此是比较合适的。集合类中的Map、关联数组具有较高的查询效率,它们的底层实现就是红黑树。 - -`多路查找树` 是大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的(如果元素数量非常多的话,查找就退化成节点内部的线性查找了),这样导致二叉查找树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下。 - -`B树`与自平衡二叉查找树不同,B树适用于读写相对大的数据块的存储系统,例如磁盘。它的应用是文件系统及部分非关系型数据库索引。 - -`B+树`在B树基础上,为叶子结点增加链表指针(B树+叶子有序链表),所有关键字都在叶子结点 中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中。通常用于关系型数据库(如Mysql)和操作系统的文件系统中。 - -针对大量数据,如果在内存中作业优先考虑红黑树(map,set之类多为RB-tree实现),如果在硬盘中作业优先考虑B系列树(B+, B, B*) - -## 相关题目 - -### 前序遍历 - -- 访问根结点; -- 先序遍历左子树; -- 先序遍历右子树。 - -```java -private void preOrder(TreeNode tree) { - if(tree != null) { - System.out.print(tree.key+" "); - preOrder(tree.left); - preOrder(tree.right); - } -} - -public void preOrder() { - preOrder(mRoot); -} -``` - -### 中序遍历 - -- 中序遍历左子树; -- 访问根结点; -- 中序遍历右子树。 - -```java -private void inOrder(TreeNode tree) { - if(tree != null) { - inOrder(tree.left); - System.out.print(tree.key+" "); - inOrder(tree.right); - } -} - -public void inOrder() { - inOrder(mRoot); -} -``` - -### 后序遍历 - -- 后序遍历左子树; -- 后序遍历右子树; -- 访问根结点。 - -```java -private void postOrder(TreeNode tree) { - if(tree != null) - { - postOrder(tree.left); - postOrder(tree.right); - System.out.print(tree.key+" "); - } -} - -public void postOrder() { - postOrder(mRoot); -} -``` - -### [100. 相同的树](https://leetcode-cn.com/problems/same-tree/) - -``` -输入: 1 1 - / \ / \ - 2 3 2 3 - - [1,2,3], [1,2,3] - -输出: true -输入: 1 1 - / \ - 2 2 - - [1,2], [1,null,2] - -输出: false -``` - -分析:递归判断 - -```java - /** - * 递归判断(实际上前行遍历) - * @param p - * @param q - * @return - */ - public boolean isSameTree(TreeNode p, TreeNode q) { - if (p == null && q == null) return true; // 递归到最后二者都是null 说明相同 - if (p == null || q == null) return false; // 递归到其中有一方为空,则不相同 - if (p.val != q.val) return false; // 值不相等 - return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); - } -``` - - - - -### [101. 对称二叉树](https://leetcode-cn.com/problems/symmetric-tree/) - -```bash - 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 - 1 - / \ - 2 2 - / \ / \ -3 4 4 3 -但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: - 1 - / \ - 2 2 - \ \ - 3 3 -``` - -分析:对称的前序遍历的值是一样的 - -```java -class Solution { - public boolean isSymmetric(TreeNode root) { - return isSym(root, root); - } - private boolean isSym(TreeNode root, TreeNode root1) { - if(root == null && root1 == null) return true; - if(root == null || root1 == null) return false; - if(root.val != root1.val) return false; - return isSym(root.left, root1.right) && isSym(root.right, root1.left); - } -} -``` - -### [104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) - -```bash -给定二叉树 [3,9,20,null,null,15,7], - - 3 - / \ - 9 20 - / \ - 15 7 -``` - -分析:递归 - -```java -class Solution { - public int maxDepth(TreeNode root) { - return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; - } -} -``` - -### [108. 将有序数组转换为二叉搜索树](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) - -```bash -给定有序数组: [-10,-3,0,5,9], - -一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: - - 0 - / \ - -3 9 - / / - -10 5 -``` - -分析:中序遍历的逆过程 左右等分建立左右子树,中间节点作为子树根节点,递归该过程 - -```java -class Solution { - public TreeNode sortedArrayToBST(int[] nums) { - return nums == null ? null : buildTree(nums, 0, nums.length - 1); - } - private TreeNode buildTree(int[] nums, int l, int r) { - if(l > r) return null; - int m = l + (r - l) / 2; - TreeNode root = new TreeNode(nums[m]); - root.left = buildTree(nums, l, m - 1); - root.right = buildTree(nums, m + 1, r); - return root; - } -} -``` - -### [110. 平衡二叉树](https://leetcode-cn.com/problems/balanced-binary-tree/) - -``` - 3 - / \ - 9 20 - / \ - 15 7 - true -``` - -```java - /** - * 自顶向下递归 - * @param root - * @return - */ - public boolean isBalanced(TreeNode root) { - if (root == null) return true; - return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 - && isBalanced(root.left) && isBalanced(root.right); - } - - public int maxDepth(TreeNode root) { - if (root == null) return 0; - return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); - } -``` - -#### [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) - -``` - 3 - / \ - 9 20 - / \ - 15 7 - - 2 -``` - -```java - /** - * 注意仔细读题,一开始没想到根左和根右任意为空的情况 - * @param root - * @return - */ - public int minDepth(TreeNode root) { - if(root == null) return 0; - int m1 = minDepth(root.left); - int m2 = minDepth(root.right); - return root.left == null || root.right == null ? m1 + m2 + 1 : Math.min(m1, m2) + 1; - } -``` - -#### [112. 路径总和](https://leetcode-cn.com/problems/path-sum/) - -``` - 5 - / \ - 4 8 - / / \ - 11 13 4 - / \ \ - 7 2 1 -sum = 22 -``` - -```java - /** - * 递归 - * @param root - * @param sum - * @return - */ - public boolean hasPathSum(TreeNode root, int sum) { - if (root == null) return false; - // 总和减去路径上节点的值 - sum -= root.val; - // 如果节的左右节点都为空,说明那个路径上走完了,可以开始比较sum是否为0 - if (root.left == null && root.right == null) return sum == 0; - return hasPathSum(root.left, sum) || hasPathSum(root.right, sum); - } -``` - -#### [226. 翻转二叉树](https://leetcode-cn.com/problems/invert-binary-tree/) - -``` - 4 - / \ - 2 7 - / \ / \ -1 3 6 9 -输出: - 4 - / \ - 7 2 - / \ / \ -9 6 3 1 -``` - -```java - /** - * 还是递归 - * @param root - * @return - */ - public TreeNode invertTree(TreeNode root) { - if (root == null) return null; - TreeNode left = invertTree(root.left); // 递归到最后的坐 - TreeNode right = invertTree(root.right); // 递归到最后的右 - // 你给我,我给你 - root.left = right; - root.right = left; - return root; - } -``` - diff --git "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\351\223\276\350\241\250.md" "b/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\351\223\276\350\241\250.md" deleted file mode 100644 index 8f227749..00000000 --- "a/LeetCode/\346\225\260\346\215\256\347\273\223\346\236\204-\351\223\276\350\241\250.md" +++ /dev/null @@ -1,453 +0,0 @@ -## 引言 - -**线性表-链表** - -## 概念 - -n个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。 确定一个链表我们只需要头指针,通过头指针就可以把整个链表都能推出来。 - - - -## 优缺点 - -### 优点 - -- 空间没有限制 -- 插入删除元素特别快 - -### 缺点 - -- 查找非常慢 - -## 分类 - -- 单向链表 一个节点指向下一个节点。 -- 双向链表 一个节点有两个指针域。 -- 循环链表 能通过任何一个节点找到其他所有的节点,将两种(双向/单向)链表的最后一个结点指向第一个结点从而实现循环。 - -## 典型题目 - -### [nkw-从尾到头打印链表](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035) - -``` -输入一个链表,按链表从尾到头的顺序返回一个ArrayList。 -``` - -分析:递归 - -```java - /** - * 递归 - * @param listNode - * @return - */ - public ArrayList printListFromTailToHead(ListNode listNode) { - ArrayList list = new ArrayList(); - if (listNode != null) { - this.printListFromTailToHead(listNode.next); - list.add(listNode.val); - } - return list; - } -``` - -分析:栈 - -```java - /** - * 栈试试 - * @param listNode - * @return - */ - public ArrayList printListFromTailToHead2(ListNode listNode) { - ArrayList list = new ArrayList(); - Stack stack = new Stack<>(); - while (listNode != null) { - stack.push(listNode.val); - listNode = listNode.next; - } - while (!stack.empty()) { - list.add(stack.pop()); - } - return list; - } -``` - -### [面试题22-链表中倒数第k个结点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/) - -``` -给定一个链表: 1->2->3->4->5, 和 k = 2. -返回链表 4->5. -``` - -分析:栈 - -```java - /** - * 栈,比较容易想得的到 - * @param head - * @param k - * @return - */ - public ListNode getKthFromEnd(ListNode head, int k) { - Stack stack = new Stack<>(); - while (head != null) { - stack.push(head); - head = head.next; - } - ListNode listNode= new ListNode(0); - for (int i = 0; i < k; i++) { - listNode = stack.pop(); - } - return listNode; - } -``` - -分析:双指针 - -```java - /** - * 双指针 - * @param head - * @param k - * @return - */ - public ListNode getKthFromEnd2(ListNode head, int k) { - ListNode pNode = head; - ListNode kNode = head; - int p1 = 0; - while (pNode != null) { - if (p1 >= k) { - kNode = kNode.next; - } - pNode = pNode.next; - p1++; - } - return kNode; - } -``` - -### [206. 反转链表](https://leetcode-cn.com/problems/reverse-linked-list/) - -``` -输入: 1->2->3->4->5->NULL -输出: 5->4->3->2->1->NULL -``` - -分析:递归或者迭代 - -```java - /** - * 迭代 - * 流程: - * 核心还是双指针... - * pre和cur一直移动 - * 接着相互指向 - * @param head - * @return - */ - public ListNode reverseList(ListNode head) { - ListNode pre = null; // 当前节点之前的节点 null - ListNode cur = head; - while (cur != null) { - ListNode nextTemp = cur.next; // 获取当前节点的下一个节点 - cur.next = pre; // 当前节点的下个节点指向前一个节点 - // 尾递归其实省了下面这两步 - pre = cur; // 将前一个节点指针移动到当前指针 - cur = nextTemp; // 当当前节点移动到下一个节点 - } - return pre; - } -``` - -分析:递归 - -```java - /** - * 递归:尾递归 - * @param head - * @return - */ - public ListNode reverseList2(ListNode head) { - return reverse(null, head); - } - - public ListNode reverse(ListNode pre, ListNode cur) { - if (cur == null) return pre; // 如果当前节点为null,直接返回 - ListNode next = cur.next; // next节点指向当前节点的下一个节点 - cur.next = pre; // 将当前节点指向 当前节点的前一个节点 - return reverse(cur, next); - } -``` - -### [21.合并两个有序链表](https://leetcode-cn.com/problems/merge-two-sorted-lists/) - -```bash -输入:1->2->4, 1->3->4 -输出:1->1->2->3->4->4 -``` - -分析:递归 - -```java - /** - * 递归 - * 比较大小,谁小,谁的下一个节点指向递归的结果 - * @param l1 - * @param l2 - * @return - */ - public ListNode mergeTwoLists(ListNode l1, ListNode l2) { - if (l1 == null) return l2; - if (l2 == null) return l1; - if (l1.val < l2.val) { - l1.next = mergeTwoLists(l1.next, l2); - return l1; - } else { - l2.next = mergeTwoLists(l1, l2.next); - return l2; - } - } -``` - -### [面试题35. 复杂链表的复制](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/) - -``` -输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] -输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] -``` - -分析:迭代,分三次 - -1. 目的是复制next,例如1->1^->2->2^ -2. 分配random -3. 双指针切断,例如1->2, 1^->2^ - -```java - /** - * 迭代三次 - * @param head - * @return - */ - public Node copyRandomList(Node head) { - if (head == null) return null; - Node node = head; // 一般这样的操作其实就是好比是指针了,链表定义个指针, 走你 - // 第一次迭代的目的是复制next - while (node != null) { - // 接下来的三步操作 复制节点 - Node temp = new Node(node.val); // new一个和node值相同的当前节点 temp 比如1` - temp.next = node.next; // temp 的下个节点指向 node的下个节点, 比如1`>2 - node.next = temp; // node 的下个节点 指向temp 比如 1 > 1` > 2 - // 一般迭代, 都会有这一步操作, 移动指针 - node = temp.next; // 将node 指针 指向 temp的下个节点, 比如2 - } - // 这次的目的让复制的节点的random 和 原先的random各个指向的一致 - // 将指针移动首部 - node = head; - while (node != null) { - // 2 2` - // ^_^ ^_^ - // 1 > 1` > 2 > 2` > 3 > 3` - node.next.random = node.random == null ? null : node.random.next; - // 迭代,移动指针 - node = node.next.next; - } - // 第三次目的是切断 返回复制的链表 - // 双指针, 重新指向 - node = head; - Node pCloneHead = head.next; - while (node != null) { - Node temp = node.next; // 其实就是当前的复制节点 - node.next = temp.next; // 其实就是 1 > 2 - temp.next = temp.next == null ? null : temp.next.next; // 其实就是 1` > 2` - // 迭代, 移动指针 - node = node.next; - } - return pCloneHead; - } -``` - -### [面试题52. 两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/) - -``` -A: a1 → a2 - ↘ - c1 → c2 → c3 - ↗ -B: b1 → b2 → b3 -``` - - - -分析:最容易想的是哈希,其次是双指针 - -```java - /** - * 最容易想的是哈希 - * headA走完一圈 - * 开始走headB,判断哪个节点和A相等,即可 - * @param headA - * @param headB - * @return - */ - public ListNode getIntersectionNode(ListNode headA, ListNode headB) { - if (headA == null || headB == null) return null; - HashSet set = new HashSet<>(); - while (headA != null) { - set.add(headA); - headA = headA.next; - } - while (headB != null) { - if (set.contains(headB)) { - return headB; - } - headB = headB.next; - } - return null; -``` - -```java - /** - * 双指针 - * @param headA - * @param headB - * @return - */ - public ListNode getIntersectionNode2(ListNode headA, ListNode headB) { - if (headA == null || headB == null) return null; - // 一男一女 - ListNode node1 = headA; - ListNode node2 = headB; - // 我走你,你走我,直到相爱 - while (node1 != node2) { - node1 = node1 == null ? headB : node1.next; - node2 = node2 == null ? headA : node2.next; - } - return node1; - } -``` - -### [141. 环形链表](https://leetcode-cn.com/problems/linked-list-cycle/) - -```bash -输入:head = [3,2,0,-4], pos = 1 -输出:true -解释:链表中有一个环,其尾部连接到第二个节点。 -``` - -分析:哈希 || 快慢指针 - -```java -/** -* 哈希 -* @param head -* @return -*/ -public class Solution { - public boolean hasCycle(ListNode head) { - HashSet set = new HashSet<>(); - while(head != null) { - if(set.contains(head)) { - return true; - } else { - set.add(head); - } - head = head.next; - } - return false; - } -} -``` - -```java -/** -* 快慢指针 -* @param head -* @return -*/ -public class Solution { - public boolean hasCycle(ListNode head) { - if(head != null && head.next != null) { - ListNode quick = head; - ListNode slow = head; - while(2 > 1) { - quick = quick.next; - if(quick == null) return false; - quick = quick.next; - if(quick == null) return false; - slow = slow.next; - if(slow == quick) return true; - } - } else { - return false; - } - } -} -``` - -### [237. 删除链表中的节点](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/) - -``` -输入: head = [4,5,1,9], node = 5 -输出: [4,1,9] -``` - -```java - public void deleteNode(ListNode node) { - node.val = node.next.val; - node.next = node.next.next; - } -``` - -### [234. 回文链表](https://leetcode-cn.com/problems/palindrome-linked-list/) - -```bash -输入: 1->2 -输出: false -输入: 1->2->2->1 -输出: true -``` - -分析:切成两半,把后半段反转,然后比较两半是否相等。 - -```java -public boolean isPalindrome(ListNode head) { - if (head == null || head.next == null) return true; - ListNode slow = head, fast = head.next; - while (fast != null && fast.next != null) { - slow = slow.next; - fast = fast.next.next; - } - if (fast != null) slow = slow.next; // 偶数节点,让 slow 指向下一个节点 - cut(head, slow); // 切成两个链表 - return isEqual(head, reverse(slow)); -} - -private void cut(ListNode head, ListNode cutNode) { - while (head.next != cutNode) { - head = head.next; - } - head.next = null; -} - -private ListNode reverse(ListNode head) { - ListNode newHead = null; - while (head != null) { - ListNode nextNode = head.next; - head.next = newHead; - newHead = head; - head = nextNode; - } - return newHead; -} - -private boolean isEqual(ListNode l1, ListNode l2) { - while (l1 != null && l2 != null) { - if (l1.val != l2.val) return false; - l1 = l1.next; - l2 = l2.next; - } - return true; -} -``` - diff --git "a/Multithread/CAS\345\272\225\345\261\202\350\247\243\346\236\220.md" "b/Multithread/CAS\345\272\225\345\261\202\350\247\243\346\236\220.md" deleted file mode 100644 index 770db424..00000000 --- "a/Multithread/CAS\345\272\225\345\261\202\350\247\243\346\236\220.md" +++ /dev/null @@ -1,126 +0,0 @@ -## 引言 - -> 大家都知道,在Java并发种,我们最初接触的应该就是`synchronized`关键字了,但是`synchronized`属于重量级锁,很多时候会引起性能问题,`volatile`也是个不错的选择,但是`volatile`不能保证原子性,只能在某些场合下使用。像`synchronized`这种独占锁属于**悲观锁**,乐观锁最常见的就是`CAS`。 - - - -## 例子 - -**我们在读Concurrent包下的类的源码时,发现无论是**ReenterLock内部的AQS,还是各种Atomic开头的原子类**,内部都应用到了`CAS`,最常见的就是我们在并发编程时遇到的`i++`这种情况。传统的方法肯定是在方法上加上`synchronized`关键字:** - -```java -public class Test { - - public volatile int i; - - public synchronized void add() { - i++; - } -} -``` - -**但是这种方法在性能上可能会差一点,我们还可以使用`AtomicInteger`,就可以保证`i`原子的`++`了。** - -```java -public class Test { - - public AtomicInteger i; - - public void add() { - i.getAndIncrement(); - } -} -``` - -**我们来看`getAndIncrement`的内部:** - -```java -public final int getAndIncrement() { - return unsafe.getAndAddInt(this, valueOffset, 1); -} -``` - -**再深入到`getAndAddInt`():** - -```java -public final int getAndAddInt(Object var1, long var2, int var4) { - int var5; - do { - var5 = this.getIntVolatile(var1, var2); - } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); - - return var5; -} -``` - -**现在重点来了,`compareAndSwapInt(var1, var2, var5, var5 + var4)`其实换成`compareAndSwapInt(obj, offset, expect, update)`比较清楚,意思就是如果`obj`内的`value`和`expect`相等,就证明没有其他线程改变过这个变量,那么就更新它为`update`,如果这一步的`CAS`没有成功,那就采用自旋的方式继续进行`CAS`操作,取出乍一看这也是两个步骤了啊,其实在`JNI`里是借助于一个`CPU`指令完成的。所以还是原子操作。** - -## CAS底层原理 - -**我们可以看到compareAndSwapInt实现是在`Unsafe_CompareAndSwapInt`里面,再深入到`Unsafe_CompareAndSwapInt`:** - -```c++ -UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) - UnsafeWrapper("Unsafe_CompareAndSwapInt"); - oop p = JNIHandles::resolve(obj); - jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); - return (jint)(Atomic::cmpxchg(x, addr, e)) == e; -UNSAFE_END -``` - -**p是取出的对象,addr是p中offset处的地址,最后调用了`Atomic::cmpxchg(x, addr, e)`, 其中参数x是即将更新的值,参数e是原内存的值。代码中能看到cmpxchg有基于各个平台的实现。** - -## CAS的问题 - -### ABA问题 - -**CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。 常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么`A-B-A` 就会变成`1A-2B-3A`。 目前在JDK的atomic包里提供了一个类`AtomicStampedReference`来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。** - -```java -public class ABADemo { - static AtomicInteger atomicInteger = new AtomicInteger(100); - static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1); - - public static void main(String[] args) { - System.out.println("=====ABA的问题产生====="); - new Thread(() -> { - atomicInteger.compareAndSet(100, 101); - atomicInteger.compareAndSet(101, 100); - }, "t1").start(); - - new Thread(() -> { - // 保证线程1完成一次ABA问题 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(atomicInteger.compareAndSet(100, 2020) + " " + atomicInteger.get()); - }, "t2").start(); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - - System.out.println("=====解决ABA的问题====="); - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); // 第一次获取版本号 - System.out.println(Thread.currentThread().getName() + " 第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); - atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp()); - }, "t3").start(); - - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); - System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } - boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1); - System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp()); - System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference()); - }, "t4").start(); - - } -} -``` - -### **循环时间长开销大** - -**上面我们说过如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开销。** - -[参考文章](https://juejin.im/post/5a73cbbff265da4e807783f5) \ No newline at end of file diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-AQS.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-AQS.md" deleted file mode 100644 index 82245725..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-AQS.md" +++ /dev/null @@ -1,365 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## AQS 简单介绍 - -AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 - -AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 - -## AQS原理 - -### 概述 - -**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** - -> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 - -![参考-JavaGuide-enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/CLH.png) - -AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 - -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` - -状态信息通过protected类型的getState,setState,compareAndSetState进行操作 - -```java - -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} -``` - -### AQS对资源的共享方式 - -**AQS定义两种资源共享方式** - -- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: - - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 -- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 - -ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 - -不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。 - -### AQS底层使用了模板方法模式 - -这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。 - -> 模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票`buyTicket()`->安检`securityCheck()`->乘坐某某工具回家`ride()`->到达目的地`arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了`ride()`方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。 - -默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 - -以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 - -再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 - -一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 - -## Semaphore(信号量)-允许多个线程同时访问 - -**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。** 示例代码如下: - -```java -public class SemaphoreExample1 { - // 请求的数量 - private static final int threadCount = 550; - - public static void main(String[] args) throws InterruptedException { - // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) - ExecutorService threadPool = Executors.newFixedThreadPool(300); - // 一次只能允许执行的线程数量。 - final Semaphore semaphore = new Semaphore(20); - - for (int i = 0; i < threadCount; i++) { - final int threadnum = i; - threadPool.execute(() -> {// Lambda 表达式的运用 - try { - semaphore.acquire();// 获取一个许可,所以可运行线程数量为20/1=20 - test(threadnum); - semaphore.release();// 释放一个许可 - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - }); - } - threadPool.shutdown(); - System.out.println("finish"); - } - - public static void test(int threadnum) throws InterruptedException { - Thread.sleep(1000);// 模拟请求的耗时操作 - System.out.println("threadnum:" + threadnum); - Thread.sleep(1000);// 模拟请求的耗时操作 - } -} -``` - -执行 `acquire` 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 `release` 方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。 - -除了 `acquire`方法之外,另一个比较常用的与之对应的方法是`tryAcquire`方法,该方法如果获取不到许可就立即返回false。 - - -Semaphore 有两种模式,公平模式和非公平模式。 - -- **公平模式:** 调用acquire的顺序就是获取许可证的顺序,遵循FIFO; -- **非公平模式:** 抢占式的。 - -**Semaphore 对应的两个构造方法如下:** - -```java - public Semaphore(int permits) { - sync = new NonfairSync(permits); - } - - public Semaphore(int permits, boolean fair) { - sync = fair ? new FairSync(permits) : new NonfairSync(permits); - } -``` - -**这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。** - -## CountDownLatch (倒计时器) - -CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。 - -### CountDownLatch 的三种典型用法 - -①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :`new CountDownLatch(n) `,每当一个任务线程执行完毕,就将计数器减1 `countdownlatch.countDown()`,当计数器的值变为0时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 - -②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1) `,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。 - -③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 - -```java -public class CountDownLatchExample1 { - // 请求的数量 - private static final int threadCount = 550; - - public static void main(String[] args) throws InterruptedException { - // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) - ExecutorService threadPool = Executors.newFixedThreadPool(300); - final CountDownLatch countDownLatch = new CountDownLatch(threadCount); - for (int i = 0; i < threadCount; i++) { - final int threadnum = i; - threadPool.execute(() -> {// Lambda 表达式的运用 - try { - test(threadnum); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } finally { - countDownLatch.countDown();// 表示一个请求已经被完成 - } - - }); - } - countDownLatch.await(); - threadPool.shutdown(); - System.out.println("finish"); - } - - public static void test(int threadnum) throws InterruptedException { - Thread.sleep(1000);// 模拟请求的耗时操作 - System.out.println("threadnum:" + threadnum); - Thread.sleep(1000);// 模拟请求的耗时操作 - } -} -``` - -上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行`System.out.println("finish");`。 - -与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。 - -其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。 - -### CountDownLatch 的不足 - -CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。 - -### CountDownLatch相常见面试题: - -解释一下CountDownLatch概念? - -CountDownLatch 和CyclicBarrier的不同之处? - -给出一些CountDownLatch使用的例子? - -CountDownLatch 类中主要的方法? - -## CyclicBarrier(循环栅栏) - -CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。 - -CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - -### CyclicBarrier 的应用场景 - -CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。 - -```java -public class CyclicBarrierExample2 { - // 请求的数量 - private static final int threadCount = 550; - // 需要同步的线程数量 - private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5); - - public static void main(String[] args) throws InterruptedException { - // 创建线程池 - ExecutorService threadPool = Executors.newFixedThreadPool(10); - - for (int i = 0; i < threadCount; i++) { - final int threadNum = i; - Thread.sleep(1000); - threadPool.execute(() -> { - try { - test(threadNum); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (BrokenBarrierException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - }); - } - threadPool.shutdown(); - } - - public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { - System.out.println("threadnum:" + threadnum + "is ready"); - try { - /**等待60秒,保证子线程完全执行结束*/ - cyclicBarrier.await(60, TimeUnit.SECONDS); - } catch (Exception e) { - System.out.println("-----CyclicBarrierException------"); - } - System.out.println("threadnum:" + threadnum + "is finish"); - } - -} -``` - -``` -threadnum:0is ready -threadnum:1is ready -threadnum:2is ready -threadnum:3is ready -threadnum:4is ready -threadnum:4is finish -threadnum:0is finish -threadnum:1is finish -threadnum:2is finish -threadnum:3is finish -threadnum:5is ready -threadnum:6is ready -threadnum:7is ready -threadnum:8is ready -threadnum:9is ready -threadnum:9is finish -threadnum:5is finish -threadnum:8is finish -threadnum:7is finish -threadnum:6is finish -...... -``` - -可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, `await`方法之后的方法才被执行。 - -另外,CyclicBarrier还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`,用于在线程到达屏障时,优先执行`barrierAction`,方便处理更复杂的业务场景。示例代码如下: - -```java -public class CyclicBarrierExample3 { - // 请求的数量 - private static final int threadCount = 550; - // 需要同步的线程数量 - private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { - System.out.println("------当线程数达到之后,优先执行------"); - }); - - public static void main(String[] args) throws InterruptedException { - // 创建线程池 - ExecutorService threadPool = Executors.newFixedThreadPool(10); - - for (int i = 0; i < threadCount; i++) { - final int threadNum = i; - Thread.sleep(1000); - threadPool.execute(() -> { - try { - test(threadNum); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (BrokenBarrierException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - }); - } - threadPool.shutdown(); - } - - public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { - System.out.println("threadnum:" + threadnum + "is ready"); - cyclicBarrier.await(); - System.out.println("threadnum:" + threadnum + "is finish"); - } - -} -``` - -``` -threadnum:0is ready -threadnum:1is ready -threadnum:2is ready -threadnum:3is ready -threadnum:4is ready -------当线程数达到之后,优先执行------ -threadnum:4is finish -threadnum:0is finish -threadnum:2is finish -threadnum:1is finish -threadnum:3is finish -threadnum:5is ready -threadnum:6is ready -threadnum:7is ready -threadnum:8is ready -threadnum:9is ready -------当线程数达到之后,优先执行------ -threadnum:9is finish -threadnum:5is finish -threadnum:6is finish -threadnum:8is finish -threadnum:7is finish -...... -``` - -### CyclicBarrier和CountDownLatch的区别 - -CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。 - -对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。 - -CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。 - -![参考-JavaGuide-CyclicBarrier和CountDownLatch的区别](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS333.png) - diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-Atomic\345\216\237\345\255\220\347\261\273.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-Atomic\345\216\237\345\255\220\347\261\273.md" deleted file mode 100644 index 6f9a8708..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-Atomic\345\216\237\345\255\220\347\261\273.md" +++ /dev/null @@ -1,208 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## Atomic 原子类介绍 - -Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 - -所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 - -JUC包中的原子类分为4类 - -**基本类型** - -使用原子的方式更新基本类型 - -- AtomicInteger:整型原子类 -- AtomicLong:长整型原子类 -- AtomicBoolean :布尔型原子类 - -**数组类型** - -使用原子的方式更新数组里的某个元素 - - -- AtomicIntegerArray:整型数组原子类 -- AtomicLongArray:长整型数组原子类 -- AtomicReferenceArray :引用类型数组原子类 - -**引用类型** - -- AtomicReference:引用类型原子类 -- AtomicReferenceFieldUpdater:原子更新引用类型里的字段 -- AtomicMarkableReference :原子更新带有标记位的引用类型 - -**对象的属性修改类型** - -- AtomicIntegerFieldUpdater:原子更新整型字段的更新器 -- AtomicLongFieldUpdater:原子更新长整型字段的更新器 -- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 -- AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 - -**CAS ABA 问题** - -- 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。 -- 例子描述(可能不太合适,但好理解): 年初,现金为零,然后通过正常劳动赚了三百万,之后正常消费了(比如买房子)三百万。年末,虽然现金零收入(可能变成其他形式了),但是赚了钱是事实,还是得交税的! -- 代码例子(以``` AtomicInteger ```为例) - -```java -import java.util.concurrent.atomic.AtomicInteger; - -public class AtomicIntegerDefectDemo { - public static void main(String[] args) { - defectOfABA(); - } - - static void defectOfABA() { - final AtomicInteger atomicInteger = new AtomicInteger(1); - - Thread coreThread = new Thread( - () -> { - final int currentValue = atomicInteger.get(); - System.out.println(Thread.currentThread().getName() + " ------ currentValue=" + currentValue); - - // 这段目的:模拟处理其他业务花费的时间 - try { - Thread.sleep(300); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - boolean casResult = atomicInteger.compareAndSet(1, 2); - System.out.println(Thread.currentThread().getName() - + " ------ currentValue=" + currentValue - + ", finalValue=" + atomicInteger.get() - + ", compareAndSet Result=" + casResult); - } - ); - coreThread.start(); - - // 这段目的:为了让 coreThread 线程先跑起来 - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - Thread amateurThread = new Thread( - () -> { - int currentValue = atomicInteger.get(); - boolean casResult = atomicInteger.compareAndSet(1, 2); - System.out.println(Thread.currentThread().getName() - + " ------ currentValue=" + currentValue - + ", finalValue=" + atomicInteger.get() - + ", compareAndSet Result=" + casResult); - - currentValue = atomicInteger.get(); - casResult = atomicInteger.compareAndSet(2, 1); - System.out.println(Thread.currentThread().getName() - + " ------ currentValue=" + currentValue - + ", finalValue=" + atomicInteger.get() - + ", compareAndSet Result=" + casResult); - } - ); - amateurThread.start(); - } -} -``` - -输出内容如下: - -``` -Thread-0 ------ currentValue=1 -Thread-1 ------ currentValue=1, finalValue=2, compareAndSet Result=true -Thread-1 ------ currentValue=2, finalValue=1, compareAndSet Result=true -Thread-0 ------ currentValue=1, finalValue=2, compareAndSet Result=true -``` - -下面我们来详细介绍一下这些原子类。 - -## 基本类型原子类 - -### 基本类型原子类介绍 - -使用原子的方式更新基本类型 - -- AtomicInteger:整型原子类 -- AtomicLong:长整型原子类 -- AtomicBoolean :布尔型原子类 - -上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。 - -**AtomicInteger 类常用方法** - -```java -public final int get() //获取当前的值 -public final int getAndSet(int newValue)//获取当前的值,并设置新的值 -public final int getAndIncrement()//获取当前的值,并自增 -public final int getAndDecrement() //获取当前的值,并自减 -public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 -boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) -public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 -``` - -### 基本数据类型原子类的优势 - -**①多线程环境不使用原子类保证线程安全(基本数据类型)** - -```java -class Test { - private volatile int count = 0; - //若要线程安全执行执行count++,需要加锁 - public synchronized void increment() { - count++; - } - - public int getCount() { - return count; - } -} -``` - -**②多线程环境使用原子类保证线程安全(基本数据类型)** - -```java -class Test2 { - private AtomicInteger count = new AtomicInteger(); - - public void increment() { - count.incrementAndGet(); - } - //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。 - public int getCount() { - return count.get(); - } -} -``` - -### AtomicInteger 线程安全原理简单分析 - -AtomicInteger 类的部分源码: - -```java - // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long valueOffset; - - static { - try { - valueOffset = unsafe.objectFieldOffset - (AtomicInteger.class.getDeclaredField("value")); - } catch (Exception ex) { throw new Error(ex); } - } - - private volatile int value; -``` - -AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 - -CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 - -### 其他 - -数组类型原子类、引用类型原子类和对象的属性修改类型原子类就不介绍了 \ No newline at end of file diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-ThreadLocal.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-ThreadLocal.md" deleted file mode 100644 index 9a7500d3..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-ThreadLocal.md" +++ /dev/null @@ -1,150 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## ThreadLocal - -### ThreadLocal简介 - -通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。** - -**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。** - -再举个简单的例子: - -比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来这两个线程竞争的。 - -### ThreadLocal示例 - -```java -import java.text.SimpleDateFormat; -import java.util.Random; - -public class ThreadLocalExample implements Runnable{ - - // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本 - private static final ThreadLocal formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm")); - - public static void main(String[] args) throws InterruptedException { - ThreadLocalExample obj = new ThreadLocalExample(); - for(int i=0 ; i<10; i++){ - Thread t = new Thread(obj, ""+i); - Thread.sleep(new Random().nextInt(1000)); - t.start(); - } - } - - @Override - public void run() { - System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern()); - try { - Thread.sleep(new Random().nextInt(1000)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - //formatter pattern is changed here by thread, but it won't reflect to other threads - formatter.set(new SimpleDateFormat()); - - System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern()); - } - -} -``` - -Output: - -``` -Thread Name= 0 default Formatter = yyyyMMdd HHmm -Thread Name= 0 formatter = yy-M-d ah:mm -Thread Name= 1 default Formatter = yyyyMMdd HHmm -Thread Name= 2 default Formatter = yyyyMMdd HHmm -Thread Name= 1 formatter = yy-M-d ah:mm -Thread Name= 3 default Formatter = yyyyMMdd HHmm -Thread Name= 2 formatter = yy-M-d ah:mm -Thread Name= 4 default Formatter = yyyyMMdd HHmm -Thread Name= 3 formatter = yy-M-d ah:mm -Thread Name= 4 formatter = yy-M-d ah:mm -Thread Name= 5 default Formatter = yyyyMMdd HHmm -Thread Name= 5 formatter = yy-M-d ah:mm -Thread Name= 6 default Formatter = yyyyMMdd HHmm -Thread Name= 6 formatter = yy-M-d ah:mm -Thread Name= 7 default Formatter = yyyyMMdd HHmm -Thread Name= 7 formatter = yy-M-d ah:mm -Thread Name= 8 default Formatter = yyyyMMdd HHmm -Thread Name= 9 default Formatter = yyyyMMdd HHmm -Thread Name= 8 formatter = yy-M-d ah:mm -Thread Name= 9 formatter = yy-M-d ah:mm -``` - -从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。 - -上面有一段代码用到了创建 `ThreadLocal` 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法`withInitial()`,将Supplier功能接口作为参数。 - -```java - private static final ThreadLocal formatter = new ThreadLocal(){ - @Override - protected SimpleDateFormat initialValue() - { - return new SimpleDateFormat("yyyyMMdd HHmm"); - } - }; -``` - -### ThreadLocal原理 - -从 `Thread`类源代码入手。 - -```java -public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; - -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... -} -``` - -从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。 - -`ThreadLocal`类的`set()`方法 - -```java - public void set(T value) { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) - map.set(this, value); - else - createMap(t, value); - } - ThreadLocalMap getMap(Thread t) { - return t.threadLocals; - } -``` - -通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。** - -**每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 - -### ThreadLocal 内存泄露问题 - -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法 - -```java - static class Entry extends WeakReference> { - /** The value associated with this ThreadLocal. */ - Object value; - - Entry(ThreadLocal k, Object v) { - super(k); - value = v; - } - } -``` - diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-synchronized.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-synchronized.md" deleted file mode 100644 index 5a127594..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-synchronized.md" +++ /dev/null @@ -1,159 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## synchronized关键字最主要的三种使用方式的总结 - -- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 -- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! - -下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 - -面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” - -**双重校验锁实现对象单例(线程安全)** - -```java -public class Singleton { - - private volatile static Singleton uniqueInstance; - - private Singleton() { - } - - public static Singleton getUniqueInstance() { - //先判断对象是否已经实例过,没有实例化过才进入加锁代码 - if (uniqueInstance == null) { - //类对象加锁 - synchronized (Singleton.class) { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - } - } - return uniqueInstance; - } -} -``` - -另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 - -uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: - -1. 为 uniqueInstance 分配内存空间 -2. 初始化 uniqueInstance -3. 将 uniqueInstance 指向分配的内存地址 - -但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 - -使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 - -## synchronized 关键字底层原理总结 - -**synchronized 关键字底层原理属于 JVM 层面。** - -**① synchronized 同步语句块的情况** - -```java -public class SynchronizedDemo { - public void method() { - synchronized (this) { - System.out.println("synchronized 代码块"); - } - } -} -``` - -通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 - -![synchronized 关键字原理](https://images.gitbook.cn/abc37c80-d21d-11e8-aab3-09d30029e0d5) - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -**② synchronized 修饰方法的的情况** - -```java -public class SynchronizedDemo2 { - public synchronized void method() { - System.out.println("synchronized 方法"); - } -} -``` - -![synchronized 关键字原理](https://images.gitbook.cn/7d407bf0-d21e-11e8-b2d6-1188c7e0dd7e) - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - -在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 - -## JDK1.6 之后的底层优化 - -JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 - -锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -**①偏向锁** - -**引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 - -偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步! - -但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。 - -**② 轻量级锁** - -倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** - -**轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!** - -**③ 自旋锁和自适应自旋** - -轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 - -互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。 - -**一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。** 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。**为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋**。 - -自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过`--XX:+UseSpinning`参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。**自旋次数的默认值是10次,用户可以修改`--XX:PreBlockSpin`来更改**。 - -另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 - -**④ 锁消除** - -锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 - -**⑤ 锁粗化** - -原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 - -大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 - -## Synchronized 和 ReenTrantLock 的对比 - -**① 两者都是可重入锁** - -两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 - -**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** - -synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 - -**③ ReenTrantLock 比 synchronized 增加了一些高级功能** - -相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** - -- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 -- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 -- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 - -如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 - -**④ 性能已不是选择标准** - -在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作**。 - diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-\345\271\266\345\217\221\345\237\272\347\241\200\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-\345\271\266\345\217\221\345\237\272\347\241\200\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" deleted file mode 100644 index c892fcb8..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-\345\271\266\345\217\221\345\237\272\347\241\200\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ /dev/null @@ -1,214 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## 什么是线程和进程? - -### 进程? - -进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。 - -在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。 - -如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。图省略 - -### 线程? - -线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 - -## 线程和进程的优缺点? - -为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢? - -### 程序计数器为什么是私有的? - -程序计数器主要有下面两个作用: - -1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 -2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 - -需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。 - -所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。 - -### 虚拟机栈和本地方法栈为什么是私有的? - -- **虚拟机栈:** 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 -- **本地方法栈:** 和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 - -所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。 - -### 一句话简单了解堆和方法区 - -堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 - -## 说说并发与并行的区别? - -- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行); -- **并行:** 单位时间内,多个任务同时执行。 - -## 为什么要使用多线程呢? - -先从总体上来说: - -- **从计算机底层来说:** 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。 -- **从当代互联网发展趋势来说:** 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。 - -再深入到计算机底层来探讨: - -- **单核时代:** 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子:当只有一个线程的时候会导致 CPU 计算时,IO 设备空闲;进行 IO 操作时,CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了,当一个线程执行 CPU 计算时,另外一个线程可以进行 IO 操作,这样两个的利用率就可以在理想情况下达到 100%了。 -- **多核时代:** 多核时代多线程主要是为了提高 CPU 利用率。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,CPU 只会一个 CPU 核心被利用到,而创建多个线程就可以让多个 CPU 核心被利用到,这样就提高了 CPU 的利用率。 - -## 使用多线程可能带来什么问题? - -并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。 - -## 说说线程的生命周期和状态? - -![](https://www.pdai.tech/_images/pics/ace830df-9919-48ca-91b5-60b193f593d2.png) - -线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。 - -当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。 - -## 什么是上下文切换? - -多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。 - -概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。 - -上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 - -Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。 - -## 什么是线程死锁?如何避免死锁? - -### 认识线程死锁 - -多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 - -如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。 - -![线程死锁示意图 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-4/2019-4%E6%AD%BB%E9%94%811.png) - -```java -public class DeadLockDemo { - private static Object resource1 = new Object();//资源 1 - private static Object resource2 = new Object();//资源 2 - - public static void main(String[] args) { - new Thread(() -> { - synchronized (resource1) { - System.out.println(Thread.currentThread() + "get resource1"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread() + "waiting get resource2"); - synchronized (resource2) { - System.out.println(Thread.currentThread() + "get resource2"); - } - } - }, "线程 1").start(); - - new Thread(() -> { - synchronized (resource2) { - System.out.println(Thread.currentThread() + "get resource2"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread() + "waiting get resource1"); - synchronized (resource1) { - System.out.println(Thread.currentThread() + "get resource1"); - } - } - }, "线程 2").start(); - } -} -Thread[线程 1,5,main]get resource1 -Thread[线程 2,5,main]get resource2 -Thread[线程 1,5,main]waiting get resource2 -Thread[线程 2,5,main]waiting get resource1 -``` - -线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过` Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。 - -学过操作系统的朋友都知道产生死锁必须具备以下四个条件: - -1. 互斥条件:该资源任意一个时刻只由一个线程占用。 -2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 -3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。 -4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 - -### 如何避免线程死锁? - -我们只要破坏产生死锁的四个条件中的其中一个就可以了。 - -**破坏互斥条件** - -这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。 - -**破坏请求与保持条件** - -一次性申请所有的资源。 - -**破坏不剥夺条件** - -占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。 - -**破坏循环等待条件** - -靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。 - -我们对线程 2 的代码修改成下面这样就不会产生死锁了。 - -```java - new Thread(() -> { - synchronized (resource1) { - System.out.println(Thread.currentThread() + "get resource1"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread() + "waiting get resource2"); - synchronized (resource2) { - System.out.println(Thread.currentThread() + "get resource2"); - } - } - }, "线程 2").start(); - -Thread[线程 1,5,main]get resource1 -Thread[线程 1,5,main]waiting get resource2 -Thread[线程 1,5,main]get resource2 -Thread[线程 2,5,main]get resource1 -Thread[线程 2,5,main]waiting get resource2 -Thread[线程 2,5,main]get resource2 - -Process finished with exit code 0 -``` - -线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。 - -## 说说 sleep() 方法和 wait() 方法区别和共同点? - -- 两者最主要的区别在于:**sleep 方法没有释放锁,而 wait 方法释放了锁** 。 -- 两者都可以暂停线程的执行。 -- Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。 -- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。 - -## 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法? - -这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来! - -new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。 - -**总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。** - diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" deleted file mode 100644 index de59126e..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ /dev/null @@ -1,734 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## synchronized 关键字 - -### 说一说自己对于 synchronized 关键字的了解 - -synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 - -另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 - -### 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 - -**synchronized关键字最主要的三种使用方式:** - -- **修饰实例方法:** 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 -- **修饰静态方法:** 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 -- **修饰代码块:** 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 - -**总结:** synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能! - -下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 - -面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” - -**双重校验锁实现对象单例(线程安全)** - -```java -public class Singleton { - - private volatile static Singleton uniqueInstance; - - private Singleton() { - } - - public static Singleton getUniqueInstance() { - //先判断对象是否已经实例过,没有实例化过才进入加锁代码 - if (uniqueInstance == null) { - //类对象加锁 - synchronized (Singleton.class) { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - } - } - return uniqueInstance; - } -} -``` - -另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 - -uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: - -1. 为 uniqueInstance 分配内存空间 -2. 初始化 uniqueInstance -3. 将 uniqueInstance 指向分配的内存地址 - -但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 - -使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 - -### 讲一下 synchronized 关键字的底层原理 - -**synchronized 关键字底层原理属于 JVM 层面。** - -**① synchronized 同步语句块的情况** - -```java -public class SynchronizedDemo { - public void method() { - synchronized (this) { - System.out.println("synchronized 代码块"); - } - } -} -``` - -**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 - -**② synchronized 修饰方法的的情况** - -```java -public class SynchronizedDemo2 { - public synchronized void method() { - System.out.println("synchronized 方法"); - } -} -``` - -synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 - -### 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 - -JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 - -锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 - -### 谈谈 synchronized和ReentrantLock 的区别 - -**① 两者都是可重入锁** - -两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 - -**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API** - -synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 - -**③ ReentrantLock 比 synchronized 增加了一些高级功能** - -相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** - -- **ReentrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 -- **ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 -- synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 - -如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。 - -**④ 性能已不是选择标准** - -## volatile关键字 - -### 讲一下Java内存模型 - -在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。 - -![数据不一致](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/数据不一致.png) - -要解决这个问题,就需要把变量声明为**volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。 - -说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。 - -![volatile关键字的可见性](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/volatile关键字的可见性.png) - -## 说说 synchronized 关键字和 volatile 关键字的区别 - -synchronized关键字和volatile关键字比较 - -- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。 -- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞** -- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。** -- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。** - -## ThreadLocal - -### ThreadLocal简介 - -通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。**如果想实现每一个线程都有自己的专属本地变量该如何解决呢?** JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。** - -**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。** - -再举个简单的例子: - -比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么ThreadLocal就是用来避免这两个线程竞争的。 - -### ThreadLocal原理 - -从 `Thread`类源代码入手。 - -```java -public class Thread implements Runnable { - ...... -//与此线程有关的ThreadLocal值。由ThreadLocal类维护 -ThreadLocal.ThreadLocalMap threadLocals = null; - -//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 -ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; - ...... -} -``` - -从上面`Thread`类 源代码可以看出`Thread` 类中有一个 `threadLocals` 和 一个 `inheritableThreadLocals` 变量,它们都是 `ThreadLocalMap` 类型的变量,我们可以把 `ThreadLocalMap` 理解为`ThreadLocal` 类实现的定制化的 `HashMap`。默认情况下这两个变量都是null,只有当前线程调用 `ThreadLocal` 类的 `set`或`get`方法时才创建它们,实际上调用这两个方法的时候,我们调用的是`ThreadLocalMap`类对应的 `get()`、`set() `方法。 - -`ThreadLocal`类的`set()`方法 - -```java - public void set(T value) { - Thread t = Thread.currentThread(); - ThreadLocalMap map = getMap(t); - if (map != null) - map.set(this, value); - else - createMap(t, value); - } - ThreadLocalMap getMap(Thread t) { - return t.threadLocals; - } -``` - -通过上面这些内容,我们足以通过猜测得出结论:**最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,`ThreadLocal` 可以理解为只是`ThreadLocalMap`的封装,传递了变量值。** `ThrealLocal` 类中可以通过`Thread.currentThread()`获取到当前线程对象后,直接通过`getMap(Thread t)`可以访问到该线程的`ThreadLocalMap`对象。 - -**每个`Thread`中都具备一个`ThreadLocalMap`,而`ThreadLocalMap`可以存储以`ThreadLocal`为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了 ThreadLocal 声明的变量为什么在每一个线程都有自己的专属本地变量。 - -### ThreadLocal 内存泄露问题 - -`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法 - -```java - static class Entry extends WeakReference> { - /** The value associated with this ThreadLocal. */ - Object value; - - Entry(ThreadLocal k, Object v) { - super(k); - value = v; - } - } -``` - -**弱引用介绍:** - -> 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 -> -> 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 - -## 线程池 - -### 为什么要用线程池? - -> **池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。** - -**线程池**提供了一种限制和管理资源(包括执行一个任务)。 每个**线程池**还维护一些基本统计信息,例如已完成任务的数量。 - -这里借用《Java 并发编程的艺术》提到的来说一下**使用线程池的好处**: - -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -### 实现Runnable接口和Callable接口的区别 - -`Runnable`自Java 1.0以来一直存在,但`Callable`仅在Java 1.5中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。 - -工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。 - -`Runnable.java` - -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} -``` - -`Callable.java` - -```java -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} -``` - -### 执行execute()方法和submit()方法的区别是什么呢? - -1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** -2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 - -我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码: - -```java - public Future submit(Runnable task) { - if (task == null) throw new NullPointerException(); - RunnableFuture ftask = newTaskFor(task, null); - execute(ftask); - return ftask; - } -``` - -上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。 - -```java - protected RunnableFuture newTaskFor(Runnable runnable, T value) { - return new FutureTask(runnable, value); - } -``` - -我们再来看看`execute()`方法: - -```java - public void execute(Runnable command) { - ... - } -``` - -### 如何创建线程池 - -《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险 - -> Executors 返回线程池对象的弊端如下: -> -> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。 -> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 - -**方式一:通过构造方法实现** - -**方式二:通过Executor 框架的工具类Executors来实现** - -我们可以创建三种类型的ThreadPoolExecutor: - -- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 -- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 -- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 - -### ThreadPoolExecutor 类分析 - -`ThreadPoolExecutor` 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。 - -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize, - int maximumPoolSize, - long keepAliveTime, - TimeUnit unit, - BlockingQueue workQueue, - ThreadFactory threadFactory, - RejectedExecutionHandler handler) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` - -#### `ThreadPoolExecutor`构造函数重要参数分析 - -**`ThreadPoolExecutor` 3 个最重要的参数:** - -- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。 -- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: - -1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁; -2. **`unit`** : `keepAliveTime` 参数的时间单位。 -3. **`threadFactory`** :executor 创建新线程的时候会用到。 -4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。 - -#### `ThreadPoolExecutor` 饱和策略 - -**`ThreadPoolExecutor` 饱和策略定义:** - -如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor` 定义一些策略: - -- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。 -- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。 -- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。 - -举个例子: Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了) - -### 一个简单的线程池Demo:`Runnable`+`ThreadPoolExecutor` - -为了让大家更清楚上面的面试题中的一些概念,我写了一个简单的线程池 Demo。 - -首先创建一个 `Runnable` 接口的实现类(当然也可以是 `Callable` 接口,我们上面也说了两者的区别。) - -`MyRunnable.java` - -```java -import java.util.Date; - -/** - * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。 - * @author shuang.kou - */ -public class MyRunnable implements Runnable { - - private String command; - - public MyRunnable(String s) { - this.command = s; - } - - @Override - public void run() { - System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date()); - processCommand(); - System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date()); - } - - private void processCommand() { - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - @Override - public String toString() { - return this.command; - } -} -``` - -编写测试程序,我们这里以阿里巴巴推荐的使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。 - -`ThreadPoolExecutorDemo.java` - -```java -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class ThreadPoolExecutorDemo { - - private static final int CORE_POOL_SIZE = 5; - private static final int MAX_POOL_SIZE = 10; - private static final int QUEUE_CAPACITY = 100; - private static final Long KEEP_ALIVE_TIME = 1L; - public static void main(String[] args) { - - //使用阿里巴巴推荐的创建线程池的方式 - //通过ThreadPoolExecutor构造函数自定义参数创建 - ThreadPoolExecutor executor = new ThreadPoolExecutor( - CORE_POOL_SIZE, - MAX_POOL_SIZE, - KEEP_ALIVE_TIME, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(QUEUE_CAPACITY), - new ThreadPoolExecutor.CallerRunsPolicy()); - - for (int i = 0; i < 10; i++) { - //创建WorkerThread对象(WorkerThread类实现了Runnable 接口) - Runnable worker = new MyRunnable("" + i); - //执行Runnable - executor.execute(worker); - } - //终止线程池 - executor.shutdown(); - while (!executor.isTerminated()) { - } - System.out.println("Finished all threads"); - } -} -``` - -可以看到我们上面的代码指定了: - -1. `corePoolSize`: 核心线程数为 5。 -2. `maximumPoolSize` :最大线程数 10 -3. `keepAliveTime` : 等待时间为 1L。 -4. `unit`: 等待时间的单位为 TimeUnit.SECONDS。 -5. `workQueue`:任务队列为 `ArrayBlockingQueue`,并且容量为 100; -6. `handler`:饱和策略为 `CallerRunsPolicy`。 - -``` -pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019 -``` - -### 线程池原理分析 - -我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会) - -现在,我们就分析上面的输出内容来简单分析一下线程池原理。 - -**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码: - -```java - // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount) - private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); - - private static int workerCountOf(int c) { - return c & CAPACITY; - } - - private final BlockingQueue workQueue; - - public void execute(Runnable command) { - // 如果任务为null,则抛出异常。 - if (command == null) - throw new NullPointerException(); - // ctl 中保存的线程池当前的一些状态信息 - int c = ctl.get(); - - // 下面会涉及到 3 步 操作 - // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize - // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。 - if (workerCountOf(c) < corePoolSize) { - if (addWorker(command, true)) - return; - c = ctl.get(); - } - // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里 - // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去 - if (isRunning(c) && workQueue.offer(command)) { - int recheck = ctl.get(); - // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。 - if (!isRunning(recheck) && remove(command)) - reject(command); - // 如果当前线程池为空就新创建一个线程并执行。 - else if (workerCountOf(recheck) == 0) - addWorker(null, false); - } - //3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。 - //如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。 - else if (!addWorker(command, false)) - reject(command); - } -``` - -通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。 - -![图解线程池实现原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/图解线程池实现原理.png) - -> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。 - -## Atomic 原子类 - -### 介绍一下Atomic 原子类 - -Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 - -所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 - -### JUC 包中的原子类是哪4类? - -**基本类型** - -使用原子的方式更新基本类型 - -- AtomicInteger:整形原子类 -- AtomicLong:长整型原子类 -- AtomicBoolean:布尔型原子类 - -**数组类型** - -使用原子的方式更新数组里的某个元素 - - -- AtomicIntegerArray:整形数组原子类 -- AtomicLongArray:长整形数组原子类 -- AtomicReferenceArray:引用类型数组原子类 - -**引用类型** - -- AtomicReference:引用类型原子类 -- AtomicStampedReference:原子更新引用类型里的字段原子类 -- AtomicMarkableReference :原子更新带有标记位的引用类型 - -**对象的属性修改类型** - -- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 -- AtomicLongFieldUpdater:原子更新长整形字段的更新器 -- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 - -### 讲讲 AtomicInteger 的使用 - -**AtomicInteger 类常用方法** - -```java -public final int get() //获取当前的值 -public final int getAndSet(int newValue)//获取当前的值,并设置新的值 -public final int getAndIncrement()//获取当前的值,并自增 -public final int getAndDecrement() //获取当前的值,并自减 -public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 -boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) -public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 -``` - - **AtomicInteger 类的使用示例** - -使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。 - -```java -class AtomicIntegerTest { - private AtomicInteger count = new AtomicInteger(); - //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 - public void increment() { - count.incrementAndGet(); - } - - public int getCount() { - return count.get(); - } -} -``` - -### 能不能给我简单介绍一下 AtomicInteger 类的原理 - -AtomicInteger 线程安全原理简单分析 - -AtomicInteger 类的部分源码: - -```java - // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) - private static final Unsafe unsafe = Unsafe.getUnsafe(); - private static final long valueOffset; - - static { - try { - valueOffset = unsafe.objectFieldOffset - (AtomicInteger.class.getDeclaredField("value")); - } catch (Exception ex) { throw new Error(ex); } - } - - private volatile int value; -``` - -AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 - -CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 - -## AQS - -AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 - -AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 - -### AQS 原理分析 - -#### AQS 原理概览 - -**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** - -> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 - -![AQS原理图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/AQS原理图.png) - -AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 - -```java -private volatile int state;//共享变量,使用volatile修饰保证线程可见性 -``` - -状态信息通过protected类型的getState,setState,compareAndSetState进行操作 - -```java - -//返回同步状态的当前值 -protected final int getState() { - return state; -} - // 设置同步状态的值 -protected final void setState(int newState) { - state = newState; -} -//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) -protected final boolean compareAndSetState(int expect, int update) { - return unsafe.compareAndSwapInt(this, stateOffset, expect, update); -} -``` - -#### AQS 对资源的共享方式 - -**AQS定义两种资源共享方式** - -- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: - - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 - - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 -- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 - -ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 - -不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。 - -#### AQS底层使用了模板方法模式 - -同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): - -1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) -2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 - -这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。 - -**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:** - -```java -isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 -tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 -tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 -tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 -tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 -``` - -默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 - -以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 - -再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 - -一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 - -### AQS 组件总结 - -- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。 -- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。 -- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 - diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213-\347\272\277\347\250\213\346\261\240.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213-\347\272\277\347\250\213\346\261\240.md" deleted file mode 100644 index b2478d9f..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213-\347\272\277\347\250\213\346\261\240.md" +++ /dev/null @@ -1,702 +0,0 @@ -## 引言 - -> [JavaGuide](https://github.com/Snailclimb/JavaGuide) :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159**,替他宣传一下子 -> -> 这位大佬,总结的真好!!!我引用这位大佬的文章,因为方便自己学习和打印... - - - -## 使用线程池的好处 - -> **池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。** - -**线程池**提供了一种限制和管理资源(包括执行一个任务)。 每个**线程池**还维护一些基本统计信息,例如已完成任务的数量。 - -这里借用《Java 并发编程的艺术》提到的来说一下**使用线程池的好处**: - -- **降低资源消耗**。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 -- **提高响应速度**。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 -- **提高线程的可管理性**。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 - -## Executor框架 - -### 简介 - -Executor 框架是 Java5 之后引进的,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。 - -> 补充:this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误。 - -Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。 - -### Executor 框架结构(主要由三大部分组成) - -#### 任务(`Runnable` /`Callable`) - -执行任务需要实现的 **`Runnable` 接口** 或 **`Callable`接口**。**`Runnable` 接口**或 **`Callable` 接口** 实现类都可以被 **`ThreadPoolExecutor`** 或 **`ScheduledThreadPoolExecutor`** 执行。 - -#### 任务的执行(`Executor`) - -任务执行机制的核心接口 **`Executor`** ,以及继承自 `Executor` 接口的 **`ExecutorService` 接口。`ThreadPoolExecutor`** 和 **`ScheduledThreadPoolExecutor`** 这两个关键类实现了 **ExecutorService 接口**。 - -**这里提了很多底层的类关系,但是,实际上我们需要更多关注的是 `ThreadPoolExecutor` 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。** - -**`ThreadPoolExecutor` 类描述:** - -```java -//AbstractExecutorService实现了ExecutorService接口 -public class ThreadPoolExecutor extends AbstractExecutorService -``` - -**`ScheduledThreadPoolExecutor` 类描述:** - -```java -//ScheduledExecutorService实现了ExecutorService接口 -public class ScheduledThreadPoolExecutor - extends ThreadPoolExecutor - implements ScheduledExecutorService -``` - -#### 异步计算的结果(`Future`) - -**`Future`** 接口以及 `Future` 接口的实现类 **`FutureTask`** 类都可以代表异步计算的结果。 - -当我们把 **`Runnable`接口** 或 **`Callable` 接口** 的实现类提交给 **`ThreadPoolExecutor`** 或 **`ScheduledThreadPoolExecutor`** 执行。(调用 `submit()` 方法时会返回一个 **`FutureTask`** 对象) - -### Executor 框架的使用示意图 - -![Executor 框架的使用示意图](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC01LTMwLzg0ODIzMzMwLmpwZw?x-oss-process=image/format,png) - -1. **主线程首先要创建实现 `Runnable` 或者 `Callable` 接口的任务对象。** -2. **把创建完成的实现 `Runnable`/`Callable`接口的 对象直接交给 `ExecutorService` 执行**: `ExecutorService.execute(Runnable command)`)或者也可以把 `Runnable` 对象或`Callable` 对象提交给 `ExecutorService` 执行(`ExecutorService.submit(Runnable task)`或 `ExecutorService.submit(Callable task)`)。 -3. **如果执行 `ExecutorService.submit(…)`,`ExecutorService` 将返回一个实现`Future`接口的对象**(我们刚刚也提到过了执行 `execute()`方法和 `submit()`方法的区别,`submit()`会返回一个 `FutureTask 对象)。由于 FutureTask` 实现了 `Runnable`,我们也可以创建 `FutureTask`,然后直接交给 `ExecutorService` 执行。 -4. **最后,主线程可以执行 `FutureTask.get()`方法来等待任务执行完成。主线程也可以执行 `FutureTask.cancel(boolean mayInterruptIfRunning)`来取消此任务的执行。** - -## (重要)ThreadPoolExecutor 类简单介绍 - -**线程池实现类 `ThreadPoolExecutor` 是 `Executor` 框架最核心的类。** - -### ThreadPoolExecutor 类分析 - -`ThreadPoolExecutor` 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。 - -```java - /** - * 用给定的初始参数创建一个新的ThreadPoolExecutor。 - */ - public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量 - int maximumPoolSize,//线程池的最大线程数 - long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 - TimeUnit unit,//时间单位 - BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 - ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 - RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务 - ) { - if (corePoolSize < 0 || - maximumPoolSize <= 0 || - maximumPoolSize < corePoolSize || - keepAliveTime < 0) - throw new IllegalArgumentException(); - if (workQueue == null || threadFactory == null || handler == null) - throw new NullPointerException(); - this.corePoolSize = corePoolSize; - this.maximumPoolSize = maximumPoolSize; - this.workQueue = workQueue; - this.keepAliveTime = unit.toNanos(keepAliveTime); - this.threadFactory = threadFactory; - this.handler = handler; - } -``` - -**下面这些对创建 非常重要,在后面使用线程池的过程中你一定会用到!所以,务必拿着小本本记清楚。** - -**`ThreadPoolExecutor` 3 个最重要的参数:** - -- **`corePoolSize` :** 核心线程数线程数定义了最小可以同时运行的线程数量。 -- **`maximumPoolSize` :** 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -- **`workQueue`:** 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,信任就会被存放在队列中。 - -`ThreadPoolExecutor`其他常见参数: - -1. **`keepAliveTime`**:当线程池中的线程数量大于 `corePoolSize` 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 `keepAliveTime`才会被回收销毁; -2. **`unit`** : `keepAliveTime` 参数的时间单位。 -3. **`threadFactory`** :executor 创建新线程的时候会用到。 -4. **`handler`** :饱和策略。关于饱和策略下面单独介绍一下。 - -下面这张图可以加深你对线程池中各个参数的相互关系的理解(图片来源:《Java性能调优实战》): - -![线程池各个参数的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/线程池各个参数的关系.jpg) - -**`ThreadPoolExecutor` 饱和策略定义:** - -如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,`ThreadPoolTaskExecutor` 定义一些策略: - -- **`ThreadPoolExecutor.AbortPolicy`**:抛出 `RejectedExecutionException`来拒绝新任务的处理。 -- **`ThreadPoolExecutor.CallerRunsPolicy`**:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。 -- **`ThreadPoolExecutor.DiscardPolicy`:** 不处理新任务,直接丢弃掉。 -- **`ThreadPoolExecutor.DiscardOldestPolicy`:** 此策略将丢弃最早的未处理的任务请求。 - -> Spring 通过 `ThreadPoolTaskExecutor` 或者我们直接通过 `ThreadPoolExecutor` 的构造函数创建线程池的时候,当我们不指定 `RejectedExecutionHandler` 饱和策略的话来配置线程池的时候默认使用的是 `ThreadPoolExecutor.AbortPolicy`。在默认情况下,`ThreadPoolExecutor` 将抛出 `RejectedExecutionException` 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 `ThreadPoolExecutor.CallerRunsPolicy`。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 `ThreadPoolExecutor` 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了。) - -### 推荐使用 `ThreadPoolExecutor` 构造函数创建线程池 - -**在《阿里巴巴 Java 开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。** - -**为什么呢?** - -> **使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源开销,解决资源不足的问题。如果不使用线程池,有可能会造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。** - -**另外《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险** - -> Executors 返回线程池对象的弊端如下: -> -> - **`FixedThreadPool` 和 `SingleThreadExecutor`** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。 -> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。 - -**方式一:通过`ThreadPoolExecutor`构造函数实现(推荐)** - -**方式二:通过 Executor 框架的工具类 Executors 来实现** - -我们可以创建三种类型的 ThreadPoolExecutor: - -- **FixedThreadPool** -- **SingleThreadExecutor** -- **CachedThreadPool** - -## (重要)ThreadPoolExecutor 使用示例 - -我们上面讲解了 `Executor`框架以及 `ThreadPoolExecutor` 类,下面让我们实战一下,来通过写一个 `ThreadPoolExecutor` 的小 Demo 来回顾上面的内容。 - -### 示例代码:`Runnable`+`ThreadPoolExecutor` - -首先创建一个 `Runnable` 接口的实现类(当然也可以是 `Callable` 接口,我们上面也说了两者的区别。) - -`MyRunnable.java` - -```java -import java.util.Date; - -/** - * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。 - * @author shuang.kou - */ -public class MyRunnable implements Runnable { - - private String command; - - public MyRunnable(String s) { - this.command = s; - } - - @Override - public void run() { - System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date()); - processCommand(); - System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date()); - } - - private void processCommand() { - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - @Override - public String toString() { - return this.command; - } -} -``` - -编写测试程序,我们这里以阿里巴巴推荐的使用 `ThreadPoolExecutor` 构造函数自定义参数的方式来创建线程池。 - -`ThreadPoolExecutorDemo.java` - -```java -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class ThreadPoolExecutorDemo { - - private static final int CORE_POOL_SIZE = 5; - private static final int MAX_POOL_SIZE = 10; - private static final int QUEUE_CAPACITY = 100; - private static final Long KEEP_ALIVE_TIME = 1L; - public static void main(String[] args) { - - //使用阿里巴巴推荐的创建线程池的方式 - //通过ThreadPoolExecutor构造函数自定义参数创建 - ThreadPoolExecutor executor = new ThreadPoolExecutor( - CORE_POOL_SIZE, - MAX_POOL_SIZE, - KEEP_ALIVE_TIME, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(QUEUE_CAPACITY), - new ThreadPoolExecutor.CallerRunsPolicy()); - - for (int i = 0; i < 10; i++) { - //创建WorkerThread对象(WorkerThread类实现了Runnable 接口) - Runnable worker = new MyRunnable("" + i); - //执行Runnable - executor.execute(worker); - } - //终止线程池 - executor.shutdown(); - while (!executor.isTerminated()) { - } - System.out.println("Finished all threads"); - } -} -``` - -可以看到我们上面的代码指定了: - -1. `corePoolSize`: 核心线程数为 5。 -2. `maximumPoolSize` :最大线程数 10 -3. `keepAliveTime` : 等待时间为 1L。 -4. `unit`: 等待时间的单位为 TimeUnit.SECONDS。 -5. `workQueue`:任务队列为 `ArrayBlockingQueue`,并且容量为 100; -6. `handler`:饱和策略为 `CallerRunsPolicy`。 - -``` -pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019 -``` - -### 线程池原理分析 - -我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会) - -现在,我们就分析上面的输出内容来简单分析一下线程池原理。 - -**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。**在 5.1 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码: - -```java - // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount) - private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); - - private static int workerCountOf(int c) { - return c & CAPACITY; - } - - private final BlockingQueue workQueue; - - public void execute(Runnable command) { - // 如果任务为null,则抛出异常。 - if (command == null) - throw new NullPointerException(); - // ctl 中保存的线程池当前的一些状态信息 - int c = ctl.get(); - - // 下面会涉及到 3 步 操作 - // 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize - // 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。 - if (workerCountOf(c) < corePoolSize) { - if (addWorker(command, true)) - return; - c = ctl.get(); - } - // 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里 - // 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去 - if (isRunning(c) && workQueue.offer(command)) { - int recheck = ctl.get(); - // 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。 - if (!isRunning(recheck) && remove(command)) - reject(command); - // 如果当前线程池为空就新创建一个线程并执行。 - else if (workerCountOf(recheck) == 0) - addWorker(null, false); - } - //3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。 - //如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。 - else if (!addWorker(command, false)) - reject(command); - } -``` - -通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。 - -![图解线程池实现原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/图解线程池实现原理.png) - -没搞懂的话,也没关系,可以看看我的分析: - -> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。 - -### 几个常见的对比 - -#### `Runnable` vs `Callable` - -`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。 - -工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。 - -`Runnable.java` - -```java -@FunctionalInterface -public interface Runnable { - /** - * 被线程执行,没有返回值也无法抛出异常 - */ - public abstract void run(); -} -``` - -`Callable.java` - -```java -@FunctionalInterface -public interface Callable { - /** - * 计算结果,或在无法这样做时抛出异常。 - * @return 计算得出的结果 - * @throws 如果无法计算结果,则抛出异常 - */ - V call() throws Exception; -} - -``` - -#### `execute()` vs `submit()` - -1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** -2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 - -我们以**`AbstractExecutorService`**接口中的一个 `submit` 方法为例子来看看源代码: - -```java - public Future submit(Runnable task) { - if (task == null) throw new NullPointerException(); - RunnableFuture ftask = newTaskFor(task, null); - execute(ftask); - return ftask; - } -``` - -上面方法调用的 `newTaskFor` 方法返回了一个 `FutureTask` 对象。 - -```java - protected RunnableFuture newTaskFor(Runnable runnable, T value) { - return new FutureTask(runnable, value); - } -``` - -我们再来看看`execute()`方法: - -```java - public void execute(Runnable command) { - ... - } -``` - -#### `shutdown()`VS`shutdownNow()` - -- **`shutdown()`** :关闭线程池,线程池的状态变为 `SHUTDOWN`。线程池不再接受新任务了,但是队列里的任务得执行完毕。 -- **`shutdownNow()`** :关闭线程池,线程的状态变为 `STOP`。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。 - -#### `isTerminated()` VS `isShutdown()` - -- **`isShutDown`** 当调用 `shutdown()` 方法后返回为 true。 -- **`isTerminated`** 当调用 `shutdown()` 方法后,并且所有提交的任务完成后返回为 true - -### `Callable`+`ThreadPoolExecutor`示例代码 - -`MyCallable.java` - -```java - -import java.util.concurrent.Callable; - -public class MyCallable implements Callable { - @Override - public String call() throws Exception { - Thread.sleep(1000); - //返回执行当前 Callable 的线程名字 - return Thread.currentThread().getName(); - } -} -``` - -`CallableDemo.java` - -```java - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -public class CallableDemo { - - private static final int CORE_POOL_SIZE = 5; - private static final int MAX_POOL_SIZE = 10; - private static final int QUEUE_CAPACITY = 100; - private static final Long KEEP_ALIVE_TIME = 1L; - - public static void main(String[] args) { - - //使用阿里巴巴推荐的创建线程池的方式 - //通过ThreadPoolExecutor构造函数自定义参数创建 - ThreadPoolExecutor executor = new ThreadPoolExecutor( - CORE_POOL_SIZE, - MAX_POOL_SIZE, - KEEP_ALIVE_TIME, - TimeUnit.SECONDS, - new ArrayBlockingQueue<>(QUEUE_CAPACITY), - new ThreadPoolExecutor.CallerRunsPolicy()); - - List> futureList = new ArrayList<>(); - Callable callable = new MyCallable(); - for (int i = 0; i < 10; i++) { - //提交任务到线程池 - Future future = executor.submit(callable); - //将返回值 future 添加到 list,我们可以通过 future 获得 执行 Callable 得到的返回值 - futureList.add(future); - } - for (Future fut : futureList) { - try { - System.out.println(new Date() + "::" + fut.get()); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - //关闭线程池 - executor.shutdown(); - } -} -``` - -``` -Wed Nov 13 13:40:41 CST 2019::pool-1-thread-1 -Wed Nov 13 13:40:42 CST 2019::pool-1-thread-2 -Wed Nov 13 13:40:42 CST 2019::pool-1-thread-3 -Wed Nov 13 13:40:42 CST 2019::pool-1-thread-4 -Wed Nov 13 13:40:42 CST 2019::pool-1-thread-5 -Wed Nov 13 13:40:42 CST 2019::pool-1-thread-3 -Wed Nov 13 13:40:43 CST 2019::pool-1-thread-2 -Wed Nov 13 13:40:43 CST 2019::pool-1-thread-1 -Wed Nov 13 13:40:43 CST 2019::pool-1-thread-4 -Wed Nov 13 13:40:43 CST 2019::pool-1-thread-5 -``` - -## 几种常见的线程池详解 - -### FixedThreadPool - -#### 介绍 - -`FixedThreadPool` 被称为可重用固定线程数的线程池。通过 Executors 类中的相关源代码来看一下相关实现: - -```java - /** - * 创建一个可重用固定数量线程的线程池 - */ - public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { - return new ThreadPoolExecutor(nThreads, nThreads, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory); - } -``` - -**从上面源代码可以看出新创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 nThreads,这个 nThreads 参数是我们使用的时候自己传递的。** - -#### 执行任务过程介绍 - -`FixedThreadPool` 的 `execute()` 方法运行示意图(该图片来源:《Java 并发编程的艺术》): - -![FixedThreadPool的execute()方法运行示意图](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC00LTE2LzcxMzc1OTYzLmpwZw?x-oss-process=image/format,png) - -**上图说明:** - -1. 如果当前运行的线程数小于 corePoolSize, 如果再来新任务的话,就创建新的线程来执行任务; -2. 当前运行的线程数等于 corePoolSize 后, 如果再来新任务的话,会将任务加入 `LinkedBlockingQueue`; -3. 线程池中的线程执行完 手头的任务后,会在循环中反复从 `LinkedBlockingQueue` 中获取任务来执行; - -#### 为什么不推荐使用`FixedThreadPool`? - -**`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`(队列的容量为 Intger.MAX_VALUE)作为线程池的工作队列会对线程池带来如下影响 :** - -1. 当线程池中的线程数达到 `corePoolSize` 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过 corePoolSize; -2. 由于使用无界队列时 `maximumPoolSize` 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 `FixedThreadPool`的源码可以看出创建的 `FixedThreadPool` 的 `corePoolSize` 和 `maximumPoolSize` 被设置为同一个值。 -3. 由于 1 和 2,使用无界队列时 `keepAliveTime` 将是一个无效参数; -4. 运行中的 `FixedThreadPool`(未执行 `shutdown()`或 `shutdownNow()`)不会拒绝任务,在任务比较多的时候会导致 OOM(内存溢出)。 - -### SingleThreadExecutor 详解 - -#### 介绍 - -`SingleThreadExecutor` 是只有一个线程的线程池。下面看看**SingleThreadExecutor 的实现:** - -```java - /** - *返回只有一个线程的线程池 - */ - public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { - return new FinalizableDelegatedExecutorService - (new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue(), - threadFactory)); - } -``` - -```java - public static ExecutorService newSingleThreadExecutor() { - return new FinalizableDelegatedExecutorService - (new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue())); - } -``` - -从上面源代码可以看出新创建的 `SingleThreadExecutor` 的 `corePoolSize` 和 `maximumPoolSize` 都被设置为 1.其他参数和 `FixedThreadPool` 相同。 - -#### 执行任务过程介绍 - -**`SingleThreadExecutor` 的运行示意图(该图片来源:《Java 并发编程的艺术》):** - -![SingleThreadExecutor的运行示意图](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC00LTE2LzgyMjc2NDU4LmpwZw?x-oss-process=image/format,png) - -**上图说明;** - -1. 如果当前运行的线程数少于 corePoolSize,则创建一个新的线程执行任务; -2. 当前线程池中有一个运行的线程后,将任务加入 `LinkedBlockingQueue` -3. 线程执行完当前的任务后,会在循环中反复从` LinkedBlockingQueue` 中获取任务来执行; - -### CachedThreadPool详解 - -#### 介绍 - -`CachedThreadPool` 是一个会根据需要创建新线程的线程池。下面通过源码来看看 `CachedThreadPool` 的实现: - -```java - /** - * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它。 - */ - public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue(), - threadFactory); - } -``` - -```java - public static ExecutorService newCachedThreadPool() { - return new ThreadPoolExecutor(0, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue()); - } -``` - -`CachedThreadPool` 的` corePoolSize` 被设置为空(0),`maximumPoolSize `被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 `maximumPool` 中线程处理任务的速度时,`CachedThreadPool` 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。 - -#### 执行任务过程介绍 - -**CachedThreadPool 的 execute()方法的执行示意图(该图片来源:《Java 并发编程的艺术》):** - -![CachedThreadPool的execute()方法的执行示意图](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC00LTE2LzE4NjExNzY3LmpwZw?x-oss-process=image/format,png) - -**上图说明:** - -1. 首先执行 `SynchronousQueue.offer(Runnable task)` 提交任务到任务队列。如果当前 `maximumPool` 中有闲线程正在执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`,那么主线程执行 offer 操作与空闲线程执行的 `poll` 操作配对成功,主线程把任务交给空闲线程执行,`execute()`方法执行完成,否则执行下面的步骤 2; -2. 当初始 `maximumPool` 为空,或者 `maximumPool` 中没有空闲线程时,将没有线程执行 `SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)`。这种情况下,步骤 1 将失败,此时 `CachedThreadPool` 会创建新线程执行任务,execute 方法执行完成; - -#### 为什么不推荐使用`CachedThreadPool`? - -`CachedThreadPool`允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。 - -## ScheduledThreadPoolExecutor 详解 - -**`ScheduledThreadPoolExecutor` 主要用来在给定的延迟后运行任务,或者定期执行任务。** 这个在实际项目中基本不会被用到,所以对这部分大家只需要简单了解一下它的思想。关于如何在Spring Boot 中 实现定时任务 - -### 简介 - -**`ScheduledThreadPoolExecutor` 使用的任务队列 `DelayQueue` 封装了一个 `PriorityQueue`,`PriorityQueue` 会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(`ScheduledFutureTask` 的 `time` 变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(`ScheduledFutureTask` 的 `squenceNumber` 变量小的先执行)。** - -**`ScheduledThreadPoolExecutor` 和 `Timer` 的比较:** - -- `Timer` 对系统时钟的变化敏感,`ScheduledThreadPoolExecutor`不是; -- `Timer` 只有一个执行线程,因此长时间运行的任务可以延迟其他任务。 `ScheduledThreadPoolExecutor` 可以配置任意数量的线程。 此外,如果你想(通过提供 ThreadFactory),你可以完全控制创建的线程; -- 在`TimerTask` 中抛出的运行时异常会杀死一个线程,从而导致 `Timer` 死机:-( ...即计划任务将不再运行。`ScheduledThreadExecutor` 不仅捕获运行时异常,还允许您在需要时处理它们(通过重写 `afterExecute` 方法`ThreadPoolExecutor`)。抛出异常的任务将被取消,但其他任务将继续运行。 - -**综上,在 JDK1.5 之后,你没有理由再使用 Timer 进行任务调度了。** - -> **备注:** Quartz 是一个由 java 编写的任务调度库,由 OpenSymphony 组织开源出来。在实际项目开发中使用 Quartz 的还是居多,比较推荐使用 Quartz。因为 Quartz 理论上能够同时对上万个任务进行调度,拥有丰富的功能特性,包括任务调度、任务持久化、可集群化、插件等等。 - -### 运行机制 - -![ScheduledThreadPoolExecutor运行机制](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC00LTE2LzkyNTk0Njk4LmpwZw?x-oss-process=image/format,png) - -**`ScheduledThreadPoolExecutor` 的执行主要分为两大部分:** - -1. 当调用 `ScheduledThreadPoolExecutor` 的 **`scheduleAtFixedRate()`** 方法或者**`scheduleWirhFixedDelay()`** 方法时,会向 `ScheduledThreadPoolExecutor` 的 **`DelayQueue`** 添加一个实现了 **`RunnableScheduledFuture`** 接口的 **`ScheduledFutureTask`** 。 -2. 线程池中的线程从 `DelayQueue` 中获取 `ScheduledFutureTask`,然后执行任务。 - -**`ScheduledThreadPoolExecutor` 为了实现周期性的执行任务,对 `ThreadPoolExecutor `做了如下修改:** - -- 使用 **`DelayQueue`** 作为任务队列; -- 获取任务的方不同 -- 执行周期任务后,增加了额外的处理 - -### ScheduledThreadPoolExecutor 执行周期任务的步骤 - -![ScheduledThreadPoolExecutor执行周期任务的步骤](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC01LTMwLzU5OTE2Mzg5LmpwZw?x-oss-process=image/format,png) - -1. 线程 1 从 `DelayQueue` 中获取已到期的 `ScheduledFutureTask(DelayQueue.take())`。到期任务是指 `ScheduledFutureTask `的 time 大于等于当前系统的时间; -2. 线程 1 执行这个 `ScheduledFutureTask`; -3. 线程 1 修改 `ScheduledFutureTask` 的 time 变量为下次将要被执行的时间; -4. 线程 1 把这个修改 time 之后的 `ScheduledFutureTask` 放回 `DelayQueue` 中(`DelayQueue.add()`)。 - -## 线程池大小确定 - -**线程池数量的确定一直是困扰着程序员的一个难题,大部分程序员在设定线程池大小的时候就是随心而定。我们并没有考虑过这样大小的配置是否会带来什么问题,我自己就是这大部分程序员中的一个代表。** - -由于笔主对如何确定线程池大小也没有什么实际经验,所以,这部分内容参考了网上很多文章/书籍。 - -**首先,可以肯定的一点是线程池大小设置过大或者过小都会有问题。合适的才是最好,貌似在 95 % 的场景下都是合适的。** - -如果阅读过我的上一篇关于线程池的文章的话,你一定知道: - -**如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。** - -**但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。** - -> 上下文切换: -> -> 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。 -> -> 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 -> -> Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。 - -有一个简单并且适用面比较广的公式: - -- **CPU 密集型任务(N+1):** 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。 -- **I/O 密集型任务(2N):** 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。 \ No newline at end of file diff --git "a/Multithread/Java\345\244\232\347\272\277\347\250\213\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/Multithread/Java\345\244\232\347\272\277\347\250\213\345\270\270\350\247\201\351\227\256\351\242\230.md" deleted file mode 100644 index 84485ae2..00000000 --- "a/Multithread/Java\345\244\232\347\272\277\347\250\213\345\270\270\350\247\201\351\227\256\351\242\230.md" +++ /dev/null @@ -1,63 +0,0 @@ -- [synchronized底层原理](/Multithread/Java多线程-synchronized.md) -**以下答案可在多线程系列中能找到** - - **说一说自己对于 synchronized 关键字的了解** - - **说说自己是怎么使用 synchronized 关键字,在项目中用到了吗** - - **synchronized关键字最主要的三种使用方式:** - - **双重校验锁实现对象单例(线程安全)** - - **讲一下 synchronized 关键字的底层原理** - - **说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗** - - **谈谈 synchronized和ReentrantLock 的区别** - - **说说 synchronized 关键字和 volatile 关键字的区别** -- [volatile](/Multithread/深刻理解volatile的一切.md) - - [volatile的可见性](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/%E6%B7%B1%E5%88%BB%E7%90%86%E8%A7%A3volatile%E7%9A%84%E4%B8%80%E5%88%87.md#%E5%8F%AF%E8%A7%81%E6%80%A7) - - [volatile的可序性](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/%E6%B7%B1%E5%88%BB%E7%90%86%E8%A7%A3volatile%E7%9A%84%E4%B8%80%E5%88%87.md#%E6%9C%89%E5%BA%8F%E6%80%A7) [单例模式](/Multithread/src/com/juc/volatiletest/VolatileVisibleDemo.java) - - [volatile不能保证原子性](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/%E6%B7%B1%E5%88%BB%E7%90%86%E8%A7%A3volatile%E7%9A%84%E4%B8%80%E5%88%87.md#%E4%B8%8D%E8%83%BD%E4%BF%9D%E8%AF%81%E5%8E%9F%E5%AD%90%E6%80%A7) - - [重排和内存屏障](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/%E6%B7%B1%E5%88%BB%E7%90%86%E8%A7%A3volatile%E7%9A%84%E4%B8%80%E5%88%87.md#%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C) -- [ThreadLocal底层原理](/Multithread/Java多线程-ThreadLocal.md) - - **介绍一下ThreadLocal是什么** - - **介绍一下底层原理** - - **说一下ThreadLocal内存泄漏问题** -- [CAS](/Multithread/CAS底层解析.md) - - [CAS-Atomic](/Multithread/src/com/juc/cas/CASDemo.java) - - [CAS-Unsafe类底层原理](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/CAS%E5%BA%95%E5%B1%82%E8%A7%A3%E6%9E%90.md#%E4%BE%8B%E5%AD%90) - - [ABA问题及解决方法](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/CAS%E5%BA%95%E5%B1%82%E8%A7%A3%E6%9E%90.md#cas%E7%9A%84%E9%97%AE%E9%A2%98) -- [Java锁机制](/Multithread/Java锁机制.md) - - [公平锁/非公平锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E5%85%AC%E5%B9%B3%E9%94%81%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81) - - [可重入锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81) - - [独享锁/共享锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E7%8B%AC%E4%BA%AB%E9%94%81%E5%85%B1%E4%BA%AB%E9%94%81) - - [互斥锁/读写锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E4%BA%92%E6%96%A5%E9%94%81%E8%AF%BB%E5%86%99%E9%94%81) - - [乐观锁/悲观锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E4%B9%90%E8%A7%82%E9%94%81%E6%82%B2%E8%A7%82%E9%94%81) - - [分段锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E5%88%86%E6%AE%B5%E9%94%81) - - [偏向锁/轻量级锁/重量级锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E5%81%8F%E5%90%91%E9%94%81%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81) - - [自旋锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E9%94%81%E6%9C%BA%E5%88%B6.md#%E8%87%AA%E6%97%8B%E9%94%81) - - [手写自旋锁](/Multithread/src/com/juc/lock/SpinLock.java) - - [手写读写锁](/Multithread/src/com/juc/lock/ReadWriteLockDemo.java) -- [死锁](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93.md#%E4%BB%80%E4%B9%88%E6%98%AF%E7%BA%BF%E7%A8%8B%E6%AD%BB%E9%94%81%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E6%AD%BB%E9%94%81) - - [死锁条件](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93.md#%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D%E7%BA%BF%E7%A8%8B%E6%AD%BB%E9%94%81) - - [死锁代码例子](/Multithread/src/com/juc/pool/DeadLockDemo.java) - - [如何找到死锁?]()**通过jdk常用的命令jsp和jstack,jsp查看java程序的id,jstack查看方法的栈信息等。** - -- [并发集合]() - - [为什么ArrayList是线程不安全的?](/Multithread/为什么说ArrayList是线程不安全.md) - - [ArrayList->Vector->SynchronizedList->CopyOnWriteArrayList](/Multithread/src/com/juc/collectiontest/ContainerNotSafeDemo.java) - - [ArraySet->SynchronizedSet->CopyOnWriteArraySet](/Multithread/src/com/juc/collectiontest/HashSetTest.java) - - [HashMap->SynchronizedMap->ConcurrentHashMap](/Multithread/src/com/juc/collectiontest/MapSafe.java) -- [AQS](/Multithread/Java多线程-AQS.md) - - [CountdownLatch-例子](/Multithread/src/com/juc/aqs/CountDownLatchDemo.java) - - [CyclicBarrier-例子](/Multithread/src/com/juc/aqs/CyclicBarrierDemo.java) - - [Semaphore-例子](/Multithread/src/com/juc/aqs/SemaphoreDemo.java) -- [阻塞队列]() - - [BlockingQueue](/Multithread/src/com/juc/queue/BlockingQueueDemo.java) - - [传统版生产者消费者模式-synchronized](/Multithread/src/com/juc/queue/ProdConsumerSynchronized.java) - - [传统版生产者消费者模式-ReentrantLock](/Multithread/src/com/juc/queue/ProdConsumerReentrantLock.java) - - [阻塞队列版本生产者消费者模式](/Multithread/src/com/juc/queue/ProdConsumerBlockingQueue.java) -- [线程池](/Multithread/Java多线程-线程池.md) - - [线程池的好处?](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#%E4%BD%BF%E7%94%A8%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%A5%BD%E5%A4%84) - - [FixedThreadPool详解](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#fixedthreadpool) - - [SingleThreadExecutor详解](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#singlethreadexecutor-%E8%AF%A6%E8%A7%A3) - - [CachedThreadPool详解](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#cachedthreadpool%E8%AF%A6%E8%A7%A3) - - [ScheduledThreadPoolExecutor详解](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#scheduledthreadpoolexecutor-%E8%AF%A6%E8%A7%A3) - - [线程池原理-ThreadPoolExecutor](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#%E9%87%8D%E8%A6%81threadpoolexecutor-%E7%B1%BB%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D) - - [ThreadPoolExecutor使用示例](/Multithread/src/com/juc/pool/ThreadPoolExecutorDemo.java) - - [线程池大小确定](https://github.com/DreamCats/JavaBooks/blob/master/Multithread/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E6%B1%A0.md#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%A4%A7%E5%B0%8F%E7%A1%AE%E5%AE%9A) - \ No newline at end of file diff --git "a/Multithread/Java\351\224\201\346\234\272\345\210\266.md" "b/Multithread/Java\351\224\201\346\234\272\345\210\266.md" deleted file mode 100644 index e56a3a5f..00000000 --- "a/Multithread/Java\351\224\201\346\234\272\345\210\266.md" +++ /dev/null @@ -1,84 +0,0 @@ -## 引言 - -> Java常见的几种锁,比如: -> -> - 公平锁/非公平锁 -> - 可重入锁 -> - 独享锁/共享锁 -> - 互斥锁/读写锁 -> - 乐观锁/悲观锁 -> - 分段锁 -> - 偏向锁/轻量级锁/重量级锁 -> - 自旋锁 - - - -## 公平锁/非公平锁 - -**公平锁指多个线程按照申请锁的顺序来获取锁。非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象(很长时间都没获取到锁-非洲人...),ReentrantLock,了解一下。** - -## 可重入锁 - -**可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,典型的synchronized,了解一下** - -```java -synchronized void setA() throws Exception { - Thread.sleep(1000); - setB(); // 因为获取了setA()的锁,此时调用setB()将会自动获取setB()的锁,如果不自动获取的话方法B将不会执行 -} -synchronized void setB() throws Exception { - Thread.sleep(1000); -} -``` - -## 独享锁/共享锁 - -- 独享锁:是指该锁一次只能被一个线程所持有。 -- 共享锁:是该锁可被多个线程所持有。 - -## 互斥锁/读写锁 - -**上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是其具体的实现** - -## 乐观锁/悲观锁 - -1. **乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待兵法同步的角度。** -2. **悲观锁认为对于同一个人数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。** -3. **乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作时没有事情的。** -4. **悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁带来大量的性能提升。** -5. **悲观锁在Java中的使用,就是利用各种锁。乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子类操作的更新。重量级锁是悲观锁的一种,自旋锁、轻量级锁与偏向锁属于乐观锁** - -## 分段锁 - -1. **分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来哦实现高效的并发操作。** - -2. ** 以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是ReentrantLock(Segment继承了ReentrantLock)** - -3. **当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。** - -4. **分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。** - -## 偏向锁/轻量级锁/重量级锁 - -1. **这三种锁是锁的状态,并且是针对Synchronized。在Java5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。** -2. **偏向锁的适用场景:始终只有一个线程在执行代码块,在它没有执行完释放锁之前,没有其它线程去执行同步快,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;在有锁竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。** -3. **轻量级锁是指当锁是偏向锁的时候,被另一个线程锁访问,偏向锁就会升级为轻量级锁,其他线程会通过自选的形式尝试获取锁,不会阻塞,提高性能。** -4. **重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。** - -## 自旋锁 - -1. **在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。** -2. **自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。** -3. **自旋锁尽可能的减少线程的阻塞,适用于锁的竞争不激烈,且占用锁时间非常短的代码来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗。** -4. **但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适用使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费。** - -## Java锁总结 - -**Java锁机制可归为Sychornized锁和Lock锁两类。Synchronized是基于JVM来保证数据同步的,而Lock则是硬件层面,依赖特殊的CPU指令来实现数据同步的。** - -- Synchronized是一个非公平、悲观、独享、互斥、可重入的重量级锁。 -- ReentrantLock是一个默认非公平但可实现公平的、悲观、独享、互斥、可重入、重量级锁。 -- ReentrantReadWriteLock是一个默认非公平但可实现公平的、悲观、写独享、读共享、读写、可重入、重量级锁。 - - - diff --git a/Multithread/README.md b/Multithread/README.md deleted file mode 100644 index 98cad86b..00000000 --- a/Multithread/README.md +++ /dev/null @@ -1,83 +0,0 @@ -## 多线程总结 - - - -## 容器 -### Map/Set -1. 不需要同步的时候可以选择 -HashMap -2. 不需要同步,需要排序可以选择 -TreeMap -3. 不需要同步,且需要双向队列或者栈的可选择 -LinkedHashMap -4. 同步,并发量小可以选择 -HashTable Collections.sychronizedXXX -5. 并发量大,可选择 -ConcurrentHashMap -6. 在5的前提下,需要排序 -ConcurrentSkipListMap - -### 队列 -1. 不需要同步,可以选择 -ArrayList LinkedList -2. 并发量小,可选择 -Collections.synchroizedXXX -CopyOnWriteList(适合大量读,少量写) -3. 并发量大,可选择 -ConcurrentLinkedQueue -BlockingQueue(无界阻塞式队列) -ArrayBQ(有界) -TransferBQ(直接转给消费者) -DelayQueue(执行定时任务) - -## 线程池 -1. Executor -2. ExecutorService -3. Callable -4. Future -5. 6种线程池 - - newFixedThreadPool(固定线程池) - - newCachedThreadPool(带有缓存线程池,默认空闲线程60s) - - newSingleThreadExecutor(单个线程) - - newScheduledThreadPoold(定时线程池) - - newWorkStealingPool(空闲线程去抢占其他线程的任务队列的任务) - - ForkJoinPool(适合大规模计算) - -## 索引 - -### 基础 -- [synchronized加锁](./src/com/basic/T1.java) -- [加锁与不加锁的区别](./src/com/basic/T2.java) -- [产生脏读问题](./src/com/basic/Account.java) -- [同步和非同步方法是否可以同时使用](./src/com/basic/T3.java) -- [synchronized是可重入锁](./src/com/basic/T4.java) -- [异常释放锁](./src/com/basic/T5.java) -- [volatile](./src/com/basic/T6.java) -- [volatile并不能保证的多线程的一致性](./src/com/basic/T7.java) -- [锁粒度](./src/com/basic/T8.java) -- [锁对象变了,锁就被释放了](./src/com/basic/T9.java) -- [不要以字符串常量作为锁定对象](./src/com/basic/T10.java) -- [线程通信](./src/com/basic/T11.java) -- [ReentrantLock](./src/com/basic/T12.java) -- [trylock](./src/com/basic/T13.java) -- [ReentrantLock的公平锁](./src/com/basic/T14.java) -- [生产者消费者](./src/com/basic/T15.java) -- [用lock和condition进行生产者和消费者](./src/com/basic/T16.java) -- [ThreadLocal](./src/com/basic/T17.java) -- [单例,内部类方式,不需要加锁](./src/com/basic/T18.java) -- [售票程序](./src/com/basic/T19.java) -- [ConcurrentMap](./src/com/basic/T20.java) -- [利用容器LinkedBlockingQueue生产者消费者](./src/com/basic/T21.java) -- [线程池-newFixedThreadPool](./src/com/basic/T22.java) -- [future](./src/com/basic/T23.java) -- [并行计算的小例子](./src/com/basic/T24.java) -- [newCachedThreadPool](./src/com/basic/T25.java) -- [newSingleThreadExecutor](./src/com/basic/T26.java) -- [newScheduledThreadPool](./src/com/basic/T27.java) -- [WorkStealingPool](./src/com/basic/T28.java) -- [ForkJoinPool](./src/com/basic/T29.java) -- [parallel的小例子](./src/com/basic/T30.java) -- [死锁的模拟](./src/com/basic/T31.java) -- [Semaphore例子](./src/com/basic/T32.java) -- [CountDownLatch例子](./src/com/basic/T33.java) -- [CyclicBarrier的使用示例](./src/com/basic/T34.java) \ No newline at end of file diff --git a/Multithread/src/com/basic/Account.java b/Multithread/src/com/basic/Account.java deleted file mode 100644 index cd3e1f89..00000000 --- a/Multithread/src/com/basic/Account.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题 - * @author: mf - * @create: 2019/12/26 21:41 - */ - -public class Account { - - String name; - - double balance; - - public synchronized void set(String name, double balance) { - this.name = name; - /** - * 故意设置2秒,让name和balance之间,受到其他事务的影响,产生脏读 - */ - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - this.balance = balance; - } - - public /*synchronized*/ double getBalance(String name) { - return this.balance; - } - - public static void main(String[] args) { - Account a = new Account(); - new Thread(() -> a.set("zhangsan", 100.0)).start(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println(a.getBalance("zhangsan")); - - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println(a.getBalance("shangsan")); - } -} diff --git a/Multithread/src/com/basic/T1.java b/Multithread/src/com/basic/T1.java deleted file mode 100644 index e5f2bef1..00000000 --- a/Multithread/src/com/basic/T1.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.basic; /** - * @program JavaBooks - * @description: synchronized关键字 - * @author: mf - * @create: 2019/12/26 21:00 - */ - -/** - * 对某个对象加锁 - */ -public class T1 { - - private int count = 10; - - private static int count1 = 10; - - private Object o = new Object(); - - public void m() { - synchronized (o) { // 给o对象加锁, o是监视器 - count--; - System.out.println(Thread.currentThread().getName() + " count = " + count); - } - } - - public void m1() { - synchronized (this) { // 任何线程要执行下面的代码,必须先拿到this的锁 - count--; - System.out.println(Thread.currentThread().getName() + " count = " + count); - } - } - - public synchronized void m2() { // 等效synchroniezd(this) - count--; - System.out.println(Thread.currentThread().getName() + " count = " + count); - } - - public static synchronized void m3() { // class对象 - count1--; - System.out.println(Thread.currentThread().getName() + " count = " + count1); - } - - public static void m4() { - synchronized (T1.class) { - count1--; - } - } -} diff --git a/Multithread/src/com/basic/T10.java b/Multithread/src/com/basic/T10.java deleted file mode 100644 index 88c23d7e..00000000 --- a/Multithread/src/com/basic/T10.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.basic; - -/** - * @program JavaBooks - * @description: 不要以字符串常量作为锁定对象 - * @author: mf - * @create: 2019/12/29 00:34 - */ - -public class T10 { - - private String s1 = "hello"; - - private String s2 = "hello"; - - void m1() { - synchronized (s1) { - - } - } - - void m2() { - synchronized (s2) { - - } - } - - // m1和m2锁的同一个字符串常量对象。 -} diff --git a/Multithread/src/com/basic/T11.java b/Multithread/src/com/basic/T11.java deleted file mode 100644 index c0f9546b..00000000 --- a/Multithread/src/com/basic/T11.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.basic; /** - * @program JavaBooks - * @description: 通信 - * @author: mf - * @create: 2019/12/30 15:54 - */ - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - * 实现一个容器,提供两个方法add size - * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束 - */ - -public class T11 { - -// List lists = new ArrayList(); - - // 解决方法1, 添加volatile,使t2能够得到通知 - volatile List lists = new ArrayList(); - - public void add(Object o) { - lists.add(o); - } - - public int size() { - return lists.size(); - } - - public static void main(String[] args) { - T11 t11 = new T11(); - - new Thread(() -> { - for (int i = 0; i < 10; i++) { - t11.add(new Object()); - System.out.println("add " + i); - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }, "t1").start(); - - - new Thread(() -> { - while (true) { - if (t11.size() == 5) { - break; - } - } - System.out.println("t2结束"); - }, "t2").start(); - } -} - - -/** - * 使用wait和notify优化 - * wait释放锁,但是notify不释放 - */ -class T11_1 { - - List lists = new ArrayList(); - - public void add(Object o) { - lists.add(o); - } - - public int size() { - return lists.size(); - } - - public static void main(String[] args) { - T11_1 t11_1 = new T11_1(); - - final Object lock = new Object(); - - new Thread(() -> { - synchronized (lock) { - System.out.println("t2 启动"); - if (t11_1.size() != 5) { - try { - lock.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println("t2 结束"); - lock.notify(); - } - }, "t2").start(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - new Thread(() -> { - System.out.println("t1 启动"); - synchronized (lock) { - for (int i = 0; i < 10; i++) { - t11_1.add(new Object()); - System.out.println("add " + i); - - if (t11_1.size() == 5) { - lock.notify(); - try { - lock.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - }, "t1").start(); - } -} - - -/** - * 使用CountDownLatch CyclicBarrier semaphore - */ -class T11_2 { - - List lists = new ArrayList(); - - public void add (Object o) { - lists.add(o); - } - - public int size() { - return lists.size(); - } - - public static void main(String[] args) { - T11_2 t11_2 = new T11_2(); - - CountDownLatch countDownLatch = new CountDownLatch(1); - - new Thread(() -> { - System.out.println("t2启动"); - if (t11_2.size() != 5) { - try { - countDownLatch.await(); // 上个门闩 - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println("t2结束"); - }, "t2").start(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - new Thread(() -> { - System.out.println("t1启动"); - for (int i = 0; i < 10; i++) { - t11_2.add(new Object()); - System.out.println("add " + i); - - if (t11_2.size() == 5) { - // 打开门闩,让t2得以执行 - countDownLatch.countDown(); - } - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }, "t1").start(); - } -} \ No newline at end of file diff --git a/Multithread/src/com/basic/T12.java b/Multithread/src/com/basic/T12.java deleted file mode 100644 index 96c5dac7..00000000 --- a/Multithread/src/com/basic/T12.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * @program JavaBooks - * @description: ReentrantLock - * @author: mf - * @create: 2019/12/30 19:51 - */ - - - -public class T12 { - - Lock lock = new ReentrantLock(); - - void m1() { - try { - lock.lock(); // 加锁 - for (int i = 0; i < 10; i++) { - System.out.println(i); - TimeUnit.SECONDS.sleep(1); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - void m2() { - lock.lock(); - System.out.println("m2..."); - } - - public static void main(String[] args) { - T12 t12 = new T12(); - new Thread(t12::m1, "t1").start(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - new Thread(t12::m2, "t2").start(); - } -} diff --git a/Multithread/src/com/basic/T13.java b/Multithread/src/com/basic/T13.java deleted file mode 100644 index ff01c157..00000000 --- a/Multithread/src/com/basic/T13.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * @program JavaBooks - * @description: trylock - * @author: mf - * @create: 2019/12/30 20:16 - */ - -public class T13 { - - Lock lock = new ReentrantLock(); - - void m1() { - try { - lock.lock(); - for (int i = 0; i < 10; i++) { - TimeUnit.SECONDS.sleep(1); - System.out.println(i); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - /** - * 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行 - * 也可以根据tryLock的返回值来判定是否锁定 - * 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unlock的处理,必须放到finally中 - */ - void m2() { - boolean locked = false; - - try { - locked = lock.tryLock(5, TimeUnit.SECONDS); - System.out.println("m2 ..." + locked); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (locked) lock.unlock(); - } - } - - public static void main(String[] args) { - T13 t13 = new T13(); - new Thread(t13::m1).start(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - new Thread(t13::m2).start(); - } -} diff --git a/Multithread/src/com/basic/T14.java b/Multithread/src/com/basic/T14.java deleted file mode 100644 index 49040545..00000000 --- a/Multithread/src/com/basic/T14.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.basic; - -import java.util.concurrent.locks.ReentrantLock; - -/** - * @program JavaBooks - * @description: ReentrantLock的公平锁 - * @author: mf - * @create: 2019/12/30 20:42 - */ - -public class T14 { - - private static ReentrantLock lock = new ReentrantLock(true); // true为公平锁 - - public void m() { - for (int i = 0; i < 1000; i++) { - lock.lock(); - try { - System.out.println(Thread.currentThread().getName() + "获得锁"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - } - - public static void main(String[] args) { - T14 t14 = new T14(); - - - new Thread(t14::m, "t1").start(); - - new Thread(t14::m, "t2").start(); - } -} diff --git a/Multithread/src/com/basic/T15.java b/Multithread/src/com/basic/T15.java deleted file mode 100644 index d385c681..00000000 --- a/Multithread/src/com/basic/T15.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.basic; - -import java.util.LinkedList; -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 写一个固定容量同步器,拥有put和get方法和getCount方法 - * @author: mf - * @create: 2019/12/30 22:28 - */ - -/* -能够支持2个生产者线程以及10个消费者线程的阻塞调用 - */ -public class T15 { - - private final LinkedList lists = new LinkedList<>(); - - private final int MAX = 10; // 最多10个元素 - - private int count = 0; - - - public synchronized void put(T t) { - while (lists.size() == MAX) { // 想想为什么用while而不是用if - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - lists.add(t); - count++; - this.notifyAll(); // 通知所有被wait挂起的线程 - } - - public synchronized T get() { - T t = null; - while (lists.size() == 0) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - t = lists.removeFirst(); - count--; - this.notifyAll(); // 通知所有被wait挂起的线程 用notify可能就死锁了。 - return t; - } - - public static void main(String[] args) { - T15 t15 = new T15<>(); - // 启动消费者线程 - for (int i = 0; i < 2; i++) { - new Thread(() -> { - for (int j = 0; j < 15; j++) System.out.println("消费者:" + t15.get()); - }, "t15_" + i).start(); - } - - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // 启动生产者线程 - for (int i = 0; i < 2; i++) { - new Thread(() -> { - for (int i1 = 0; i1 < 15; i1++) { - t15.put("生产者ID:" + Thread.currentThread().getName() + " 编号:" + i1); - } - }, "p" + i).start(); - } - } -} diff --git a/Multithread/src/com/basic/T16.java b/Multithread/src/com/basic/T16.java deleted file mode 100644 index bcb0df3e..00000000 --- a/Multithread/src/com/basic/T16.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.basic; - -import java.util.LinkedList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * @program JavaBooks - * @description: 用lock和condition进行生产者和消费者 - * @author: mf - * @create: 2019/12/30 23:28 - */ - -public class T16 { - - private final LinkedList lists = new LinkedList<>(); - - private final int MAX = 10; - - private int count = 0; - - private Lock lock = new ReentrantLock(); - - private Condition producer = lock.newCondition(); - - private Condition consumer = lock.newCondition(); - - public void put(T t) { - try { - lock.lock(); - while (lists.size() == MAX) { - producer.await(); // 空间满了, 那就能先站着看手机把。。。 - } - lists.add(t); - count++; - consumer.signalAll(); // 把消费者全部叫醒,吃东西 - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - - public T get() { - T t = null; - try { - lock.lock(); - while (lists.size() == 0) { - consumer.await(); - } - t = lists.removeFirst(); - count--; - producer.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - return t; - } - - public static void main(String[] args) { - T16 t16 = new T16<>(); - - // new 四个消费者 - for (int i = 0; i < 5; i++) { - new Thread(() -> { - for (int i1 = 0; i1 < 4; i1++) { - System.out.println(t16.get()); - } - }, "消费者:" + i).start(); - } - - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // 来2个生产者 - for (int i = 0; i < 2; i++) { - new Thread(() -> { - for (int i1 = 0; i1 < 10; i1++) { - t16.put(Thread.currentThread().getName() + "产品id:" + i1); - } - }, "生产者id:" + i).start(); - } - } -} diff --git a/Multithread/src/com/basic/T17.java b/Multithread/src/com/basic/T17.java deleted file mode 100644 index cb7d5282..00000000 --- a/Multithread/src/com/basic/T17.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: ThreadLocal - * @author: mf - * @create: 2019/12/30 23:55 - */ - -public class T17 { - - private static ThreadLocal tl = new ThreadLocal<>(); - - public static void main(String[] args) { - new Thread(() -> { - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(tl.get()); // 无法得到线程二的new的person - }).start(); - - new Thread(() -> { - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - tl.set(new person()); - }).start(); - } -} - - -class person { - String name = "zhangsan"; -} diff --git a/Multithread/src/com/basic/T18.java b/Multithread/src/com/basic/T18.java deleted file mode 100644 index 1c7e0517..00000000 --- a/Multithread/src/com/basic/T18.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.basic; - -import java.util.Arrays; - -/** - * @program JavaBooks - * @description: 单例,内部类方式,不需要加锁 - * @author: mf - * @create: 2019/12/31 11:17 - */ - -public class T18 { - - private T18() { - System.out.println("single"); - } - - private static class Inner { - private static T18 s = new T18(); - } - - private static T18 getSingle() { - return Inner.s; - } - - public static void main(String[] args) { - Thread[] ths = new Thread[100]; - for (int i = 0; i < ths.length; i++) { - ths[i] = new Thread(() -> { - T18.getSingle(); - }); - } - Arrays.asList(ths).forEach(o -> o.start()); - } -} diff --git a/Multithread/src/com/basic/T19.java b/Multithread/src/com/basic/T19.java deleted file mode 100644 index 117f7ed7..00000000 --- a/Multithread/src/com/basic/T19.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.basic; /** - * @program JavaBooks - * @description: 售票程序 - * @author: mf - * @create: 2019/12/31 18:36 - */ - -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import java.util.Vector; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.TimeUnit; - -/** - * 有N张火车票,每张票都有一个编号 - * 同时有10个窗口对外售票 - * - */ -public class T19 { - - private static List tickets = new ArrayList<>(); - - static { - for (int i = 0; i < 1000; i++) { - tickets.add("票编号:" + i); - } - } - - public static void main(String[] args) { - // 10个窗口售票 - for (int i = 0; i < 10; i++) { - new Thread(() -> { - while (tickets.size() > 0) { - // size和remove不是原子性的 - System.out.println("销售了--" + tickets.remove(0)); - } - }).start(); - } - } -} - -/** - * 用vector安全容器 - */ -class T19_1 { - - private static Vector lists = new Vector<>(); - - static { - for (int i = 0; i < 1000; i++) { - lists.add("编号:" + i); - } - } - - public static void main(String[] args) { - for (int i = 0; i < 10; i++) { - new Thread(() -> { - while (lists.size() > 0) { - // 加点延迟 - try { - TimeUnit.MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // 但是size和remove之间不是原子性的 - System.out.println("销售了--" + lists.remove(0)); - } - }).start(); - } - } -} - -/** - * 用synchroized - */ -class T19_2 { - - private static List lists = new ArrayList<>(); - - static { - for (int i = 0; i < 1000; i++) { - lists.add("编号:" + i); - } - } - - public static void main(String[] args) { - for (int i = 0; i < 10; i++) { - new Thread(() -> { - while (true) { - synchronized (lists) { - if (lists.size() <= 0) break; - - try { - TimeUnit.MILLISECONDS.sleep(10); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println("销售了--" + lists.remove(0)); - } - } - }).start(); - } - } -} - - -/** - * 使用并发容器队列 - */ -class T19_3 { - - private static Queue lists = new ConcurrentLinkedDeque<>(); - - static { - for (int i = 0; i < 1000; i++) { - lists.add("编号:" + i); - } - } - - public static void main(String[] args) { - for (int i = 0; i < 10; i++) { - new Thread(() -> { - while (true) { - String s = lists.poll(); - if (s == null) break; - else System.out.println("销售了--" + s); - } - }).start(); - } - } -} \ No newline at end of file diff --git a/Multithread/src/com/basic/T2.java b/Multithread/src/com/basic/T2.java deleted file mode 100644 index 3ecc71f5..00000000 --- a/Multithread/src/com/basic/T2.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.basic; - -/** - * @program JavaBooks - * @description: 加锁和不加锁输出 - * @author: mf - * @create: 2019/12/26 21:19 - */ - -public class T2 implements Runnable{ - - private int count = 10; - - @Override - public /*synchronized*/ void run() { - count--; - System.out.println(Thread.currentThread().getName() + " count = " + count); - } - - public static void main(String[] args) { - T2 t2 = new T2(); - for (int i = 0; i < 5; i++) { // 5个线程共同访问T2这个对象 - new Thread(t2, "thread" + i).start(); - } - } -} diff --git a/Multithread/src/com/basic/T20.java b/Multithread/src/com/basic/T20.java deleted file mode 100644 index bcdebccf..00000000 --- a/Multithread/src/com/basic/T20.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.basic; - -import java.util.Arrays; -import java.util.Hashtable; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; - -/** - * @program JavaBooks - * @description: ConcurrentMap - * @author: mf - * @create: 2019/12/31 19:59 - */ - -public class T20 { - - public static void main(String[] args) { -// Map map = new Hashtable<>(); - Map map = new ConcurrentHashMap<>(); - Random r = new Random(); - Thread[] ths = new Thread[100]; // 100个线程 - CountDownLatch latch = new CountDownLatch(ths.length); - long start = System.currentTimeMillis(); - for (int i = 0; i < ths.length; i++) { - ths[i] = new Thread(() -> { - for (int i1 = 0; i1 < 10000; i1++) { - map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000)); - } - latch.countDown(); - }); - } - Arrays.asList(ths).forEach(t -> t.start()); - try { - latch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - long end = System.currentTimeMillis(); - System.out.println(end -start); - } -} diff --git a/Multithread/src/com/basic/T21.java b/Multithread/src/com/basic/T21.java deleted file mode 100644 index 14760862..00000000 --- a/Multithread/src/com/basic/T21.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.basic; - -import java.util.Queue; -import java.util.Random; -import java.util.concurrent.*; - -/** - * @program JavaBooks - * @description: 利用容器LinkedBlockingQueue生产者消费者 - * @author: mf - * @create: 2019/12/31 22:26 - */ - -public class T21 { - -// private static Queue strs = new ConcurrentLinkedDeque<>(); - -// private static BlockingQueue strs = new ArrayBlockingQueue<>(10); // 有界队列 - -// private static LinkedTransferQueue strs = new LinkedTransferQueue<>(); // 更高的高并发,先找消费者 - - private static BlockingQueue strs = new LinkedBlockingDeque<>(); // 无界队列 - - private static Random r = new Random(); - - public static void main(String[] args) { - new Thread(() -> { - for (int i = 0; i < 100; i++) { - try { - strs.put("a" + i); - TimeUnit.MILLISECONDS.sleep(r.nextInt(1000)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }, "p1").start(); - - for (int i = 0; i < 5; i++) { - new Thread(() -> { - for (;;) { - try { - System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }, "c" + i).start(); - } - } -} diff --git a/Multithread/src/com/basic/T22.java b/Multithread/src/com/basic/T22.java deleted file mode 100644 index c1f4df8d..00000000 --- a/Multithread/src/com/basic/T22.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.basic; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 线程池 - * @author: mf - * @create: 2020/01/01 14:09 - */ - -public class T22 { - - public static void main(String[] args) throws InterruptedException { - //第一个,固定线程池 - ExecutorService service = Executors.newFixedThreadPool(5); - - for (int i = 0; i < 6; i++) { - service.execute(() -> { - try { - TimeUnit.MILLISECONDS.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName()); - }); - } - - System.out.println(service); - service.shutdown(); - System.out.println(service.isTerminated()); - System.out.println(service.isShutdown()); - System.out.println(service); - - TimeUnit.SECONDS.sleep(5); - - System.out.println(service.isTerminated()); - System.out.println(service.isShutdown()); - System.out.println(service); - } -} diff --git a/Multithread/src/com/basic/T23.java b/Multithread/src/com/basic/T23.java deleted file mode 100644 index fd577109..00000000 --- a/Multithread/src/com/basic/T23.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.basic; - -import java.util.concurrent.*; - -/** - * @program JavaBooks - * @description: future - * @author: mf - * @create: 2020/01/01 14:18 - */ - -public class T23 { - public static void main(String[] args) throws ExecutionException, InterruptedException { - FutureTask task = new FutureTask<>(() -> { - TimeUnit.MILLISECONDS.sleep(500); - return 1000; - }); - - new Thread(task).start(); - - System.out.println(task.get()); // 阻塞 - - ExecutorService service = Executors.newFixedThreadPool(5); - - Future f = service.submit(() -> { - try { - TimeUnit.MILLISECONDS.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return 1; - }); -// System.out.println(f.get()); - System.out.println(f.isDone()); - System.out.println(f.get()); - System.out.println(f.isDone()); - service.shutdown(); - } -} diff --git a/Multithread/src/com/basic/T24.java b/Multithread/src/com/basic/T24.java deleted file mode 100644 index 7c2baac5..00000000 --- a/Multithread/src/com/basic/T24.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.basic; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -/** - * @program JavaBooks - * @description: 并行计算的小例子 - * @author: mf - * @create: 2020/01/01 14:26 - */ - -public class T24 { - public static void main(String[] args) throws ExecutionException, InterruptedException { - long start = System.currentTimeMillis(); - List results = getPrime(1, 200000); - long end = System.currentTimeMillis(); - System.out.println(end - start); - - System.out.println(Runtime.getRuntime().availableProcessors());// 看看自己的cpu几核 - - // 但算了, 还是取4把 - final int cpuCoreNum = 4; - - ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum); - - MyTask t1 = new MyTask(1, 80000); - MyTask t2 = new MyTask(80001, 130000); - MyTask t3 = new MyTask(130001, 170000); - MyTask t4 = new MyTask(170001, 200000); - - Future> f1 = service.submit(t1); - Future> f2 = service.submit(t2); - Future> f3 = service.submit(t3); - Future> f4 = service.submit(t4); - - start = System.currentTimeMillis(); - f1.get(); - f2.get(); - f3.get(); - f4.get(); - end = System.currentTimeMillis(); - System.out.println(end - start); - - service.shutdown(); - } - - static class MyTask implements Callable> { - - int startPos, endPos; - - public MyTask(int startPos, int endPos) { - this.startPos = startPos; - this.endPos = endPos; - } - - @Override - public List call() throws Exception { - List r = getPrime(startPos, endPos); - return r; - } - } - - - /** - * 判断该数是否为质数 - * @param num - * @return - */ - private static boolean isPrime(int num) { - for (int i = 2; i <= num / 2; i++) { - if (num % i == 0) return false; - } - return true; - } - - /** - * 在一定范围内的质数 - * @param start - * @param end - * @return - */ - private static List getPrime(int start, int end) { - List results = new ArrayList<>(); - for (int i = start; i <= end; i++) { - if (isPrime(i)) results.add(i); - } - return results; - } -} diff --git a/Multithread/src/com/basic/T25.java b/Multithread/src/com/basic/T25.java deleted file mode 100644 index 36f2a1f9..00000000 --- a/Multithread/src/com/basic/T25.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.basic; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 第二种,newCachedThreadPool - * @author: mf - * @create: 2020/01/01 14:47 - */ - -public class T25 { - public static void main(String[] args) throws InterruptedException { - ExecutorService service = Executors.newCachedThreadPool(); - System.out.println(service); - - for (int i = 0; i < 2; i++) { - service.execute(() -> { - try { - TimeUnit.MILLISECONDS.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName()); - }); - } - - System.out.println(service); - - TimeUnit.SECONDS.sleep(65); - - System.out.println(service); - - service.shutdown(); - } -} diff --git a/Multithread/src/com/basic/T26.java b/Multithread/src/com/basic/T26.java deleted file mode 100644 index 78b33cef..00000000 --- a/Multithread/src/com/basic/T26.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.basic; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * @program JavaBooks - * @description: 第三种,single - * @author: mf - * @create: 2020/01/01 14:56 - */ - - -/** - * 尽管是线程池中的单个线程,但是比那种每次创建个线程,系统开销大的稍微好点。。。 - */ -public class T26 { - public static void main(String[] args) { - ExecutorService service = Executors.newSingleThreadExecutor(); - for (int i = 0; i < 5; i++) { - final int j = i; - service.execute(() -> { - System.out.println(j + " " + Thread.currentThread().getName()); - }); - } - service.shutdown(); - } -} diff --git a/Multithread/src/com/basic/T27.java b/Multithread/src/com/basic/T27.java deleted file mode 100644 index 000344a4..00000000 --- a/Multithread/src/com/basic/T27.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.basic; - -import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 第四种,schedule - * @author: mf - * @create: 2020/01/01 15:00 - */ - -public class T27 { - public static void main(String[] args) { - ScheduledExecutorService service = Executors.newScheduledThreadPool(4); - service.scheduleAtFixedRate(() -> { - try { - TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName()); - }, 0, 500, TimeUnit.MICROSECONDS); - - service.shutdown(); - } -} diff --git a/Multithread/src/com/basic/T28.java b/Multithread/src/com/basic/T28.java deleted file mode 100644 index 0e3dda73..00000000 --- a/Multithread/src/com/basic/T28.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.basic; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 第五种,WorkStealingPool - * @author: mf - * @create: 2020/01/01 15:05 - */ - - -public class T28 { - public static void main(String[] args) throws IOException { - // 默认核数 - ExecutorService service = Executors.newWorkStealingPool(); - - service.execute(new R(1000)); - service.execute(new R(2000)); - service.execute(new R(2000)); - service.execute(new R(2000)); - service.execute(new R(2000)); - - // 由于产生的是精灵线程(守护线程、后台线程),主线程如果不阻塞的话,看不到输出 - System.in.read(); - - service.shutdown(); - } - - private static class R implements Runnable { - - int time; - - public R(int time) { - this.time = time; - } - - @Override - public void run() { - try { - TimeUnit.MILLISECONDS.sleep(time); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName()); - } - } -} diff --git a/Multithread/src/com/basic/T29.java b/Multithread/src/com/basic/T29.java deleted file mode 100644 index 81b276fd..00000000 --- a/Multithread/src/com/basic/T29.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.basic; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Random; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.RecursiveAction; -import java.util.concurrent.RecursiveTask; - -/** - * @program JavaBooks - * @description: 第六种,ForkJoinPool - * @author: mf - * @create: 2020/01/01 15:12 - */ - -public class T29 { - - private static int[] nums = new int[1000000]; - - private static final int MAX_NUM = 50000; - - private static Random r = new Random(); - - static { - for (int i = 0; i < nums.length; i++) { - nums[i] = r.nextInt(100); - } - System.out.println(Arrays.stream(nums).sum()); - } - - /** - * 没有返回值 - */ -// private static class AddTask extends RecursiveAction { -// -// int start, end; -// -// public AddTask(int start, int end) { -// this.start = start; -// this.end = end; -// } -// -// @Override -// protected void compute() { -// if (end - start <= MAX_NUM) { -// long sum = 0L; -// for (int i = start; i < end; i++) { -// sum += nums[i]; -// } -// System.out.println("from:" + start + "to:" + end + " = " + sum); -// } else { -// int middle = start + (end - start) >> 1; -// AddTask subTask1 = new AddTask(start, middle); -// AddTask subTask2 = new AddTask(middle, end); -// subTask1.fork(); -// subTask2.fork(); -// } -// } -// } - - private static class AddTask extends RecursiveTask { - - int start, end; - - public AddTask(int start, int end) { - this.start = start; - this.end = end; - } - - @Override - protected Long compute() { - if (end - start <= MAX_NUM) { - long sum = 0L; - for (int i = start; i < end; i++) { - sum += nums[i]; - } - return sum; - } - int middle = start + (end - start) >> 1; - AddTask subTask1 = new AddTask(start, middle); - AddTask subTask2 = new AddTask(middle, end); - subTask1.fork(); - subTask2.fork(); - return subTask1.join() + subTask2.join(); - } - } - - public static void main(String[] args) throws IOException { - ForkJoinPool fjp = new ForkJoinPool(); - AddTask task = new AddTask(0, nums.length); - fjp.execute(task); - System.in.read(); - } -} diff --git a/Multithread/src/com/basic/T3.java b/Multithread/src/com/basic/T3.java deleted file mode 100644 index 7a7057bb..00000000 --- a/Multithread/src/com/basic/T3.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.basic; /** - * @program JavaBooks - * @description: 同步和非同步方法是否可以同时使用 - * @author: mf - * @create: 2019/12/26 21:30 - */ - -/** - * m1方法执行过程当中,能否执行m2 - * 可以执行 - */ -public class T3 { - - public synchronized void m1() { // 锁定的对象的同步方法 - System.out.println(Thread.currentThread().getName() + " m1 start ..."); - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName() + " m1 end ..."); - } - - public void m2() { - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName() + " m2 "); - } - - public static void main(String[] args) { - T3 t3 = new T3(); - - new Thread(() -> t3.m1(), "t1").start(); - new Thread(() -> t3.m2(), "t2").start(); - } -} diff --git a/Multithread/src/com/basic/T30.java b/Multithread/src/com/basic/T30.java deleted file mode 100644 index c5f5798d..00000000 --- a/Multithread/src/com/basic/T30.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.basic; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * @program JavaBooks - * @description: parallel的小例子 - * @author: mf - * @create: 2020/01/01 16:04 - */ - -public class T30 { - public static void main(String[] args) { - List nums = new ArrayList<>(); - Random r = new Random(); - for (int i = 0; i < 10000; i++) nums.add(100000 + r.nextInt(100000)); - - long start = System.currentTimeMillis(); - nums.forEach(v -> isPrime(v)); - long end = System.currentTimeMillis(); - System.out.println(end - start); - - // 使用parallel stream api - start = System.currentTimeMillis(); - nums.parallelStream().forEach(T30::isPrime); - end = System.currentTimeMillis(); - System.out.println(end - start); - } - - - private static boolean isPrime(int num) { - for (int i = 2; i < num / 2; i++) { - if (num % i == 0) return false; - } - return true; - } -} diff --git a/Multithread/src/com/basic/T31.java b/Multithread/src/com/basic/T31.java deleted file mode 100644 index 7148e904..00000000 --- a/Multithread/src/com/basic/T31.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.basic; /** - * @program JavaBooks - * @description: 死锁的模拟 - * @author: mf - * @create: 2020/01/09 13:57 - */ - - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * 用线程池模拟,比较方便一些 - */ -public class T31 { - - private static Object res1 = new Object(); // 资源1 - - private static Object res2 = new Object(); // 资源2 - - public void m1() { - synchronized (res1) { - // 占用res1的锁 - System.out.println(Thread.currentThread().getName() + " get res1"); - try { - Thread.sleep(1000); // 延时一会,等待m2的方法占用res2 - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println("waiting get res2"); - synchronized (res2) { - System.out.println(Thread.currentThread().getName() + " get res2"); - } - } - } - - public void m2() { - synchronized (res2) { - // 占用res2的锁 - System.out.println(Thread.currentThread().getName() + " get res2"); - try { - Thread.sleep(1000); // - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println("waiting get res1"); - synchronized (res1) { - System.out.println(Thread.currentThread().getName() + " get res1"); - } - } - } - - public static void main(String[] args) { - T31 t31 = new T31(); - ExecutorService service = Executors.newCachedThreadPool(); - service.execute(() -> t31.m1()); - service.execute(() -> t31.m2()); - } -} diff --git a/Multithread/src/com/basic/T32.java b/Multithread/src/com/basic/T32.java deleted file mode 100644 index f3ca44e7..00000000 --- a/Multithread/src/com/basic/T32.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.basic; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Semaphore; - -/** - * @program JavaBooks - * @description: Semaphore例子 - * @author: mf - * @create: 2020/01/09 15:35 - */ - -public class T32 { - - // 请求的数量 - private static final int threadCount = 20; - - public static void main(String[] args) { - ExecutorService service = Executors.newFixedThreadPool(10); - - // 一次只能允许执行的线程数量 - final Semaphore semaphore = new Semaphore(5); - - for (int i = 0; i < threadCount; i++) { - final int threadNum = i; - service.execute(() -> { - try { - semaphore.acquire(); // 获取一个许可,所以可运行线程数量为5/1=5 - Thread.sleep(1000); - System.out.println("threadNum: " + threadNum); - Thread.sleep(1000); - semaphore.release(); // 释放一个许可 - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - } - service.shutdown(); - System.out.println("finish..."); - } -} diff --git a/Multithread/src/com/basic/T33.java b/Multithread/src/com/basic/T33.java deleted file mode 100644 index 00b0f8f6..00000000 --- a/Multithread/src/com/basic/T33.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.basic; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * @program JavaBooks - * @description: CountDownLatch例子 - * @author: mf - * @create: 2020/01/09 16:52 - */ - -public class T33 { - - // 请求的数量 - private static final int threadCount= 30; - - public static void main(String[] args) throws InterruptedException { - ExecutorService service = Executors.newFixedThreadPool(10); - CountDownLatch countDownLatch = new CountDownLatch(threadCount); - for (int i = 0; i < threadCount; i++) { - final int threadNum = i; - service.execute(() -> { - try { - Thread.sleep(500); - System.out.println("threadNum: " + threadNum); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - countDownLatch.countDown(); - } - }); - } - countDownLatch.await(); - service.shutdown(); - System.out.println("finish..."); - } -} diff --git a/Multithread/src/com/basic/T34.java b/Multithread/src/com/basic/T34.java deleted file mode 100644 index 98298ea5..00000000 --- a/Multithread/src/com/basic/T34.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.basic; - -import java.util.concurrent.*; - -/** - * @program JavaBooks - * @description: CyclicBarrier的使用示例 - * @author: mf - * @create: 2020/01/09 17:17 - */ - -public class T34 { - - // 请求的数量 - private static final int threadCount = 30; - - // 需要同步的线程数量 - private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5); - - public static void main(String[] args) throws InterruptedException { - ExecutorService service = Executors.newFixedThreadPool(10); - - for (int i = 0; i < threadCount; i++) { - final int threadNum = i; - Thread.sleep(1000); - service.execute(() -> { - test(threadNum); - }); - } - service.shutdown(); - } - - private static void test(int threadNum) { - System.out.println("threadNum: " + threadNum + " is ready"); - try { - cyclicBarrier.await(60, TimeUnit.SECONDS); // 等待60s,保证子线程完全执行结束 - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (BrokenBarrierException e) { - e.printStackTrace(); - } catch (TimeoutException e) { - e.printStackTrace(); - } - System.out.println("threadNum: " + threadNum + " finish..."); - } -} diff --git a/Multithread/src/com/basic/T4.java b/Multithread/src/com/basic/T4.java deleted file mode 100644 index 19369d63..00000000 --- a/Multithread/src/com/basic/T4.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 一个同步方法可以调用另外一个同步方法,synchronized是可重入锁 子类同步也可以调用父类的同步方法 - * @author: mf - * @create: 2019/12/26 22:00 - */ - -public class T4 { - - synchronized void m1() { - System.out.println("m1 start"); - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - m2(); - } - - synchronized void m2() { - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println("m2"); - } -} diff --git a/Multithread/src/com/basic/T5.java b/Multithread/src/com/basic/T5.java deleted file mode 100644 index f6f612d2..00000000 --- a/Multithread/src/com/basic/T5.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 异常释放锁 - * @author: mf - * @create: 2019/12/27 23:38 - */ - -public class T5 { - - private int count = 0; - - public synchronized void m() { - System.out.println(Thread.currentThread().getName() + " start... "); - while (true) { - count++; - System.out.println(Thread.currentThread().getName() + " count = " + count); - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (count == 5) { - int i = 1 / 0; // 此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续 - } - } - } - - public static void main(String[] args) { - T5 t5 = new T5(); - - new Thread(() -> t5.m(), "t1").start(); - - try { - TimeUnit.SECONDS.sleep(3); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - new Thread(() -> t5.m(), "t2").start(); - } -} diff --git a/Multithread/src/com/basic/T6.java b/Multithread/src/com/basic/T6.java deleted file mode 100644 index 4fb49503..00000000 --- a/Multithread/src/com/basic/T6.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: volatile关键字 - * @author: mf - * @create: 2019/12/28 22:54 - */ - -public class T6 { - - /*volatile*/ boolean running = true; // 对比一下有无valotile的情况下,整个程序运行结果的区别 - - void m() { - System.out.println("m start"); - while (running) { - // 啥都没得 -// try { -// TimeUnit.SECONDS.sleep(1); -// } catch (InterruptedException e) { -// e.printStackTrace(); -// } - } - System.out.println("m end ..."); - } - - public static void main(String[] args) { - T6 t6 = new T6(); - - new Thread(() -> t6.m(), "t1").start(); - - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - t6.running = false; - } -} diff --git a/Multithread/src/com/basic/T7.java b/Multithread/src/com/basic/T7.java deleted file mode 100644 index c96acc30..00000000 --- a/Multithread/src/com/basic/T7.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.basic; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @program JavaBooks - * @description: volatile并不能保证的多线程的一致性 - * @author: mf - * @create: 2019/12/28 23:46 - */ - -public class T7 { - - AtomicInteger count = new AtomicInteger(0); - -// /*volatile*/ int count = 0; - - /*synchronized*/ void m() { - for (int i = 0; i < 1000; i++) { -// count++; - count.incrementAndGet(); - } - } - - public static void main(String[] args) { - T7 t7 = new T7(); - - List threads = new ArrayList<>(); - - for (int i = 0; i < 10; i++) { - threads.add(new Thread(t7::m, "thread-" + i)); - } - - threads.forEach((o) -> o.start()); - - threads.forEach((o) -> { - try { - o.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - - System.out.println(t7.count); - } -} diff --git a/Multithread/src/com/basic/T8.java b/Multithread/src/com/basic/T8.java deleted file mode 100644 index 4076613e..00000000 --- a/Multithread/src/com/basic/T8.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 锁粒度 - * @author: mf - * @create: 2019/12/29 00:23 - */ - -public class T8 { - - private int count = 0; - - synchronized void m1() { - // 锁粒度较粗,锁的东西较多 - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - count++; - - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - void m2() { - // 锁粒度较细, 锁的东西较少, 运行时间比上面快 - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - synchronized (this) { - count++; // - } - } -} diff --git a/Multithread/src/com/basic/T9.java b/Multithread/src/com/basic/T9.java deleted file mode 100644 index e8b8a7c8..00000000 --- a/Multithread/src/com/basic/T9.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.basic; - -import java.util.concurrent.TimeUnit; - -/** - * @program JavaBooks - * @description: 锁对象变了,锁就被释放了 - * @author: mf - * @create: 2019/12/29 00:28 - */ - -public class T9 { - - Object o = new Object(); - - void m() { - synchronized (o) { - while (true) { - try { - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println(Thread.currentThread().getName()); // 打印自己的名字 - } - } - } - - public static void main(String[] args) { - T9 t9 = new T9(); - - new Thread(t9::m, "t9").start(); - - try { - TimeUnit.SECONDS.sleep(3); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - t9.o = new Object(); // 注释掉这句话的话, t9二代是不会启动的,因为还在上锁呢。 - - new Thread(t9::m, "t9二代").start(); - } -} diff --git a/Multithread/src/com/juc/aqs/CountDownLatchDemo.java b/Multithread/src/com/juc/aqs/CountDownLatchDemo.java deleted file mode 100644 index cbdf17cc..00000000 --- a/Multithread/src/com/juc/aqs/CountDownLatchDemo.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @program JavaBooks - * @description: CountDownLatchDemo - * @author: mf - * @create: 2020/02/15 17:09 - */ - -package com.juc.aqs; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public class CountDownLatchDemo { - - public static void main(String[] args) throws InterruptedException { - countDownLatchTest(); -// general(); - } - - public static void general() { - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - }, "Thread --> " + i).start(); - } - while (Thread.activeCount() > 2) { - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } - } - - public static void countDownLatchTest() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(6); - for (int i = 0; i < 6; i++) { - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 上完自习,离开教师"); - countDownLatch.countDown(); - }, "Thread --> " + i).start(); - } - countDownLatch.await(); - System.out.println(Thread.currentThread().getName() + " ====班长最后走人"); - } -} - - - diff --git a/Multithread/src/com/juc/aqs/CyclicBarrierDemo.java b/Multithread/src/com/juc/aqs/CyclicBarrierDemo.java deleted file mode 100644 index d470f753..00000000 --- a/Multithread/src/com/juc/aqs/CyclicBarrierDemo.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @program JavaBooks - * @description: CyclicBarrierDemo - * @author: mf - * @create: 2020/02/15 17:21 - */ - -package com.juc.aqs; - -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CyclicBarrier; - -public class CyclicBarrierDemo { - public static void main(String[] args) { - cyclicBarrierTest(); - } - - public static void cyclicBarrierTest() { - CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { - System.out.println("====召唤神龙===="); - }); - for (int i = 0; i < 7; i++) { - final int tempInt = i; - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 收集到第" + tempInt + "颗龙珠"); - try { - cyclicBarrier.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (BrokenBarrierException e) { - e.printStackTrace(); - } - }, "" + i).start(); - } - } -} diff --git a/Multithread/src/com/juc/aqs/SemaphoreDemo.java b/Multithread/src/com/juc/aqs/SemaphoreDemo.java deleted file mode 100644 index 12fe367d..00000000 --- a/Multithread/src/com/juc/aqs/SemaphoreDemo.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: SemaphoreDemo - * @author: mf - * @create: 2020/02/16 00:13 - */ - -package com.juc.aqs; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -public class SemaphoreDemo { - public static void main(String[] args) { - Semaphore semaphore = new Semaphore(3);// 模拟三个停车位 - for (int i = 0; i < 6; i++) { // 模拟6部汽车 - new Thread(() -> { - try { - semaphore.acquire(); - System.out.println(Thread.currentThread().getName() + " 抢到车位"); - // 停车3s - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(Thread.currentThread().getName() + " 停车3s后离开车位"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - semaphore.release(); - } - }, "Car " + i).start(); - } - } -} diff --git a/Multithread/src/com/juc/cas/ABADemo.java b/Multithread/src/com/juc/cas/ABADemo.java deleted file mode 100644 index 5fa0700a..00000000 --- a/Multithread/src/com/juc/cas/ABADemo.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @program JavaBooks - * @description: ABADemo,ABA问题的小例子 - * @author: mf - * @create: 2020/02/14 00:17 - */ - -package com.juc.cas; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicStampedReference; - -public class ABADemo { - static AtomicInteger atomicInteger = new AtomicInteger(100); - static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1); - - public static void main(String[] args) { - System.out.println("=====ABA的问题产生====="); - new Thread(() -> { - atomicInteger.compareAndSet(100, 101); - atomicInteger.compareAndSet(101, 100); - }, "t1").start(); - - new Thread(() -> { - // 保证线程1完成一次ABA问题 - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println(atomicInteger.compareAndSet(100, 2020) + " " + atomicInteger.get()); - }, "t2").start(); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - - System.out.println("=====解决ABA的问题====="); - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); // 第一次获取版本号 - System.out.println(Thread.currentThread().getName() + " 第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); - atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); - System.out.println(Thread.currentThread().getName() + "\t第3次版本号" + atomicStampedReference.getStamp()); - }, "t3").start(); - - new Thread(() -> { - int stamp = atomicStampedReference.getStamp(); - System.out.println(Thread.currentThread().getName() + "\t第1次版本号" + stamp); - try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } - boolean result = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1); - System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp()); - System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" + atomicStampedReference.getReference()); - }, "t4").start(); - - } -} diff --git a/Multithread/src/com/juc/cas/CASDemo.java b/Multithread/src/com/juc/cas/CASDemo.java deleted file mode 100644 index 0557f735..00000000 --- a/Multithread/src/com/juc/cas/CASDemo.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @program JavaBooks - * @description: compare and swap - * @author: mf - * @create: 2020/02/13 14:48 - */ - -package com.juc.cas; - -import java.util.concurrent.atomic.AtomicInteger; - -public class CASDemo { - public static void main(String[] args) { - AtomicInteger atomicInteger = new AtomicInteger(5); - System.out.println(atomicInteger.compareAndSet(5, 2020) + - " current value: " + atomicInteger.get()); - System.out.println(atomicInteger.compareAndSet(5, 1024) + - " current value: " + atomicInteger.get()); - } -} diff --git a/Multithread/src/com/juc/collectiontest/ContainerNotSafeDemo.java b/Multithread/src/com/juc/collectiontest/ContainerNotSafeDemo.java deleted file mode 100644 index 8604686d..00000000 --- a/Multithread/src/com/juc/collectiontest/ContainerNotSafeDemo.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @program JavaBooks - * @description: ContainerNotSafeDemo - * @author: mf - * @create: 2020/02/14 11:52 - */ - -package com.juc.collectiontest; - -import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; - -public class ContainerNotSafeDemo { - public static void main(String[] args) { -// notSafe(); -// vectorTest(); -// synchronizedTest(); - copyOnWriteTest(); - } - - /** - * 故障现象 - * java.util.ConcurrentModificationException - */ - public static void notSafe() { - List list = new ArrayList<>(); - for (int i = 1; i <= 10; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } - - /** - * Vector - */ - public static void vectorTest() { - List list = new Vector<>(); - for (int i = 1; i <= 10; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } - - /** - * Collections.synchronizedList - */ - public static void synchronizedTest() { - List list = Collections.synchronizedList(new ArrayList<>()); - for (int i = 1; i <= 10; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } - - /** - * copyOnWriteArrayList - */ - public static void copyOnWriteTest() { - List list = new CopyOnWriteArrayList<>(); - for (int i = 1; i <= 10; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } - -} diff --git a/Multithread/src/com/juc/collectiontest/HashSetTest.java b/Multithread/src/com/juc/collectiontest/HashSetTest.java deleted file mode 100644 index 4aebc3a2..00000000 --- a/Multithread/src/com/juc/collectiontest/HashSetTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @program JavaBooks - * @description: HashSetTest - * @author: mf - * @create: 2020/02/14 17:21 - */ - -package com.juc.collectiontest; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArraySet; - -public class HashSetTest { - public static void main(String[] args) { -// notSafe(); -// safe1(); - safe2(); - } - /** - * 故障现象 - * java.util.ConcurrentModificationException - */ - public static void notSafe() { - Set list = new HashSet<>(); - for (int i = 1; i <= 30; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } - public static void safe1() { - Set list = Collections.synchronizedSet(new HashSet<>()); - for (int i = 1; i <= 30; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } - - public static void safe2() { - Set list = new CopyOnWriteArraySet<>(); - for (int i = 1; i <= 30; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } -} diff --git a/Multithread/src/com/juc/collectiontest/MapSafe.java b/Multithread/src/com/juc/collectiontest/MapSafe.java deleted file mode 100644 index 94b3d428..00000000 --- a/Multithread/src/com/juc/collectiontest/MapSafe.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @program JavaBooks - * @description: MapSafe - * @author: mf - * @create: 2020/02/14 17:24 - */ - -package com.juc.collectiontest; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class MapSafe { - public static void main(String[] args) { - notSafe(); - } - - public static void notSafe() { -// Map map = new HashMap<>(); -// Map map = new ConcurrentHashMap<>(); - Map map = Collections.synchronizedMap(new HashMap<>()); - - for (int i = 1; i <= 30; i++) { - new Thread(() -> { - map.put(Thread.currentThread().getName(), UUID.randomUUID().toString()); - System.out.println(map); - }, "Thread " + i).start(); - } - } -} diff --git a/Multithread/src/com/juc/lock/ReadWriteLockDemo.java b/Multithread/src/com/juc/lock/ReadWriteLockDemo.java deleted file mode 100644 index d5a18074..00000000 --- a/Multithread/src/com/juc/lock/ReadWriteLockDemo.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @program JavaBooks - * @description: ReadWriteLockDemo - * 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。 - * 如果有一个线程象取写共享资源来,就不应该自由其他线程可以对资源进行读或写 - * 因此: - * 读读能共存 - * 读写不能共存 - * 写写不能共存 - * @author: mf - * @create: 2020/02/15 14:31 - */ - -package com.juc.lock; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class ReadWriteLockDemo { - public static void main(String[] args) { - Mycache mycache = new Mycache(); - for (int i = 1; i <= 5; i++) { - final int tempInt = i; - new Thread(() -> { - mycache.put(tempInt + "", tempInt + ""); - }, "Thread " + i).start(); - } - - for (int i = 1; i <= 5; i++) { - final int tempInt = i; - new Thread(() -> { - mycache.get(tempInt + ""); - }, "Thread " + i).start(); - } - - for (int i = 1; i <= 5; i++) { - final int tempInt = i; - new Thread(() -> { - mycache.put(tempInt + "", tempInt * 2); - }, "Thread====" + i).start(); - } - } -} - -class Mycache { - private volatile Map map = new HashMap<>(); - private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); - - /** - * 写操作:原子+独占 - * @param key - * @param value - */ - public void put(String key, Object value) { - rwLock.writeLock().lock(); - try { - System.out.println(Thread.currentThread().getName() + " 正在写入:" + key); - TimeUnit.MILLISECONDS.sleep(300); - map.put(key, value); - System.out.println(Thread.currentThread().getName() + " 写入完成"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - rwLock.writeLock().unlock(); - } - } - - public void get(String key) { - rwLock.readLock().lock(); - try { - System.out.println(Thread.currentThread().getName() + " 正在读取:" + key); - TimeUnit.SECONDS.sleep(3); - Object result = map.get(key); - System.out.println(Thread.currentThread().getName() + " 读取完成:" + result); - } catch (Exception e) { - e.printStackTrace(); - } finally { - rwLock.readLock().unlock(); - } - } -} \ No newline at end of file diff --git a/Multithread/src/com/juc/lock/SpinLock.java b/Multithread/src/com/juc/lock/SpinLock.java deleted file mode 100644 index 2df9d289..00000000 --- a/Multithread/src/com/juc/lock/SpinLock.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @program JavaBooks - * @description: 手写自旋锁 - * 通过CAS操作完成自旋锁,A线程先进来调用mylock方法自己持有锁5秒钟,B随后进来发现当前有线程持有锁,不是null, - * 所以只能通过自旋等待,知道A释放锁后B随后抢到 - * @author: mf - * @create: 2020/02/15 12:20 - */ - -package com.juc.lock; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -public class SpinLock { - - // 原子引用线程 - AtomicReference atomicReference = new AtomicReference<>(); - - public void mylock() { - Thread thread = Thread.currentThread(); - System.out.println(Thread.currentThread().getName() + " como in..."); - while (!atomicReference.compareAndSet(null, thread)) { -// System.out.println("不爽,重新获取一次值瞧瞧..."); - } - } - - public void myUnlock() { - Thread thread = Thread.currentThread(); - atomicReference.compareAndSet(thread, null); - System.out.println(Thread.currentThread().getName() + " invoke myUnLock..."); - } - - public static void main(String[] args) { - SpinLock spinLock = new SpinLock(); - new Thread(() -> { - spinLock.mylock(); - try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } - spinLock.myUnlock(); - }, "t1").start(); - try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } - - new Thread(() -> { - spinLock.mylock(); - spinLock.myUnlock(); - }, "t2").start(); - } - -} diff --git a/Multithread/src/com/juc/pool/DeadLockDemo.java b/Multithread/src/com/juc/pool/DeadLockDemo.java deleted file mode 100644 index d2b7c285..00000000 --- a/Multithread/src/com/juc/pool/DeadLockDemo.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @program JavaBooks - * @description: DeadLockDemo - * @author: mf - * @create: 2020/02/17 19:01 - */ - -package com.juc.pool; - -import java.util.concurrent.TimeUnit; - -public class DeadLockDemo { - public static void main(String[] args) { - String lockA = "lockA"; - String lockB = "lockB"; - new Thread(new HoldThread(lockA, lockB), "lockAAA").start(); - new Thread(new HoldThread(lockB, lockA), "lockBBB").start(); - - // 通过jsp和jstack可以配合查看死锁 - } -} - -class HoldThread implements Runnable { - - private String lockA; - - private String lockB; - - public HoldThread(String lockA, String lockB) { - this.lockA = lockA; - this.lockB = lockB; - } - - @Override - public void run() { - synchronized (lockA) { - System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockA + "\t尝试获得:" + lockB); - // 为了看的更清楚 - try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } - synchronized (lockB) { - System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockB + "\t尝试获得:" + lockA); - } - } - } -} \ No newline at end of file diff --git a/Multithread/src/com/juc/pool/ThreadPoolExecutorDemo.java b/Multithread/src/com/juc/pool/ThreadPoolExecutorDemo.java deleted file mode 100644 index cfeac9dd..00000000 --- a/Multithread/src/com/juc/pool/ThreadPoolExecutorDemo.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @program JavaBooks - * @description: ThreadPoolExecutorDemo - * @author: mf - * @create: 2020/02/17 00:37 - */ - -package com.juc.pool; - -import java.util.concurrent.*; - -public class ThreadPoolExecutorDemo { - public static void main(String[] args) { - ExecutorService threadpools = new ThreadPoolExecutor( - 3, - 5, - 1l, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(3), - Executors.defaultThreadFactory(), - new ThreadPoolExecutor.AbortPolicy()); -//new ThreadPoolExecutor.AbortPolicy(); -//new ThreadPoolExecutor.CallerRunsPolicy(); -//new ThreadPoolExecutor.DiscardOldestPolicy(); -//new ThreadPoolExecutor.DiscardPolicy(); - try { - for (int i = 0; i < 8; i++) { - threadpools.execute(() -> { - System.out.println(Thread.currentThread().getName() + "\t办理业务"); - }); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - threadpools.shutdown(); - } - } -} diff --git a/Multithread/src/com/juc/queue/BlockingQueueDemo.java b/Multithread/src/com/juc/queue/BlockingQueueDemo.java deleted file mode 100644 index c0a13ff7..00000000 --- a/Multithread/src/com/juc/queue/BlockingQueueDemo.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @program JavaBooks - * @description: BlockingQueueDemo - * - ArrayBlockingQueue:由数组结构组成的有界阻塞队列. - * - LinkedBlockingQueue:由链表结构组成的有界(但大小默认值Integer>MAX_VALUE)阻塞队列. - * - PriorityBlockingQueue:支持优先级排序的无界阻塞队列. - * - DelayQueue:使用优先级队列实现的延迟无界阻塞队列. - * - SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列. - * - LinkedTransferQueue:由链表结构组成的无界阻塞队列. - * - LinkedBlockingDuque:由了解结构组成的双向阻塞队列. - * 方法: - * - 抛出异常方法:add remove - * - 不抛异常:offer poll - * - 阻塞 put take - * - 带时间 offer poll - * @author: mf - * @create: 2020/02/16 11:19 - */ - -package com.juc.queue; - -public class BlockingQueueDemo { -} diff --git a/Multithread/src/com/juc/queue/ProdConsumerBlockingQueue.java b/Multithread/src/com/juc/queue/ProdConsumerBlockingQueue.java deleted file mode 100644 index 98d62977..00000000 --- a/Multithread/src/com/juc/queue/ProdConsumerBlockingQueue.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @program JavaBooks - * @description: ProdConsumerBlockingQueue - * @author: mf - * @create: 2020/02/16 14:20 - */ - -package com.juc.queue; - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -public class ProdConsumerBlockingQueue { - - private volatile boolean flag = true; - - private AtomicInteger atomicInteger = new AtomicInteger(); - - BlockingQueue blockingQueue = null; - - public ProdConsumerBlockingQueue(BlockingQueue blockingQueue) { - this.blockingQueue = blockingQueue; - } - - public void myProd() throws Exception { - String data = null; - boolean retValue; - while (flag) { - data = atomicInteger.incrementAndGet() + ""; - retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS); - if (retValue) { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 成功"); - } else { - System.out.println(Thread.currentThread().getName() + " 插入队列" + data + " 失败"); - } - TimeUnit.SECONDS.sleep(1); - } - System.out.println(Thread.currentThread().getName() + " 大老板叫停了,flag=false,生产结束"); - } - - public void myConsumer() throws Exception { - String result = null; - while (flag) { - result = blockingQueue.poll(2, TimeUnit.SECONDS); - if (null == result || result.equalsIgnoreCase("")) { - flag = false; - System.out.println(Thread.currentThread().getName() + " 超过2s没有取到蛋糕,消费退出"); - return; - } - System.out.println(Thread.currentThread().getName() + " 消费队列" + result + "成功"); - } - } - - public void stop() { - flag = false; - } - - public static void main(String[] args) { - ProdConsumerBlockingQueue prodConsumerBlockingQueue = new ProdConsumerBlockingQueue(new ArrayBlockingQueue<>(10)); - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 生产线程启动"); - try { - prodConsumerBlockingQueue.myProd(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Prod").start(); - - new Thread(() -> { - System.out.println(Thread.currentThread().getName() + " 消费线程启动"); - try { - prodConsumerBlockingQueue.myConsumer(); - } catch (Exception e) { - e.printStackTrace(); - } - }, "Consumer").start(); - - try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } - System.out.println("5s后main叫停,线程结束"); - prodConsumerBlockingQueue.stop(); - } -} diff --git a/Multithread/src/com/juc/queue/ProdConsumerReentrantLock.java b/Multithread/src/com/juc/queue/ProdConsumerReentrantLock.java deleted file mode 100644 index 05a4c9a2..00000000 --- a/Multithread/src/com/juc/queue/ProdConsumerReentrantLock.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @program JavaBooks - * @description: ProdConsumerReentrantLock - * @author: mf - * @create: 2020/02/16 13:43 - */ - -package com.juc.queue; - -import java.util.LinkedList; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public class ProdConsumerReentrantLock { - - private LinkedList lists = new LinkedList<>(); - - private Lock lock = new ReentrantLock(); - - private Condition prod = lock.newCondition(); - - private Condition cons = lock.newCondition(); - - public void put(String s) { - lock.lock(); - try { - // 1. 判断 - while (lists.size() != 0) { - // 等待不能生产 - prod.await(); - } - // 2.干活 - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - // 3. 通知 - cons.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public void get() { - lock.lock(); - try { - // 1. 判断 - while (lists.size() == 0) { - // 等待不能消费 - cons.await(); - } - // 2.干活 - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - // 3. 通知 - prod.signalAll(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - lock.unlock(); - } - } - - public static void main(String[] args) { - ProdConsumerReentrantLock prodConsumerReentrantLock = new ProdConsumerReentrantLock(); - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerReentrantLock.put(tempI + ""); - }, "ProdA" + i).start(); - } - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerReentrantLock::get, "ConsA" + i).start(); - } - } -} diff --git a/Multithread/src/com/juc/queue/ProdConsumerSynchronized.java b/Multithread/src/com/juc/queue/ProdConsumerSynchronized.java deleted file mode 100644 index 63832e50..00000000 --- a/Multithread/src/com/juc/queue/ProdConsumerSynchronized.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * @program JavaBooks - * @description: ProdConsumerSynchronized - * synchronized版本的生产者和消费者,比较繁琐 - * 能够支持2个生产者线程以及10个消费者线程的阻塞调用 - * @author: mf - * @create: 2020/02/16 13:01 - */ - -package com.juc.queue; - -import java.util.LinkedList; - -public class ProdConsumerSynchronized { - - private final LinkedList lists = new LinkedList<>(); - - public synchronized void put(String s) { - while (lists.size() != 0) { // 用while怕有存在虚拟唤醒线程 - // 满了, 不生产了 - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - lists.add(s); - System.out.println(Thread.currentThread().getName() + " " + lists.peekFirst()); - this.notifyAll(); // 这里可是通知所有被挂起的线程,包括其他的生产者线程 - } - - public synchronized void get() { - while (lists.size() == 0) { - try { - this.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - System.out.println(Thread.currentThread().getName() + " " + lists.removeFirst()); - this.notifyAll(); // 通知所有被wait挂起的线程 用notify可能就死锁了。 - } - - public static void main(String[] args) { - ProdConsumerSynchronized prodConsumerSynchronized = new ProdConsumerSynchronized(); - - // 启动消费者线程 - for (int i = 0; i < 5; i++) { - new Thread(prodConsumerSynchronized::get, "ConsA" + i).start(); - } - - // 启动生产者线程 - for (int i = 0; i < 5; i++) { - int tempI = i; - new Thread(() -> { - prodConsumerSynchronized.put("" + tempI); - }, "ProdA" + i).start(); - } - - - - - - - - - } -} diff --git a/Multithread/src/com/juc/volatiletest/VolatileAtomicDemo.java b/Multithread/src/com/juc/volatiletest/VolatileAtomicDemo.java deleted file mode 100644 index 60510834..00000000 --- a/Multithread/src/com/juc/volatiletest/VolatileAtomicDemo.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @program JavaBooks - * @description: 不能保证原子 - * @author: mf - * @create: 2020/02/13 01:16 - */ - -package com.juc.volatiletest; - -public class VolatileAtomicDemo { - public static volatile int count = 0; - - public static void increase() { - count++; - } - - public static void main(String[] args) { - Thread[] threads = new Thread[20]; - for(int i = 0; i < threads.length; i++) { - threads[i] = new Thread(() -> { - for(int j = 0; j < 1000; j++) { - increase(); - } - }); - threads[i].start(); - } - //等待所有累加线程结束 - while (Thread.activeCount() > 1) { - Thread.yield(); - } - System.out.println(count); - } -} diff --git a/Multithread/src/com/juc/volatiletest/VolatileBarrierExample.java b/Multithread/src/com/juc/volatiletest/VolatileBarrierExample.java deleted file mode 100644 index c58500f7..00000000 --- a/Multithread/src/com/juc/volatiletest/VolatileBarrierExample.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @program JavaBooks - * @description: VolatileBarrierExample - * @author: mf - * @create: 2020/02/13 13:08 - */ - -package com.juc.volatiletest; - -public class VolatileBarrierExample { - int a; - volatile int v1 = 1; - volatile int v2 = 2; - - void readAndWrite() { - int i = v1; //第一个volatile读 - int j = v2; // 第二个volatile读 - a = i + j; //普通写 - v1 = i + 1; // 第一个volatile写 - v2 = j * 2; //第二个 volatile写 - } -} diff --git a/Multithread/src/com/juc/volatiletest/VolatileClassDemo.java b/Multithread/src/com/juc/volatiletest/VolatileClassDemo.java deleted file mode 100644 index db765ba2..00000000 --- a/Multithread/src/com/juc/volatiletest/VolatileClassDemo.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @program JavaBooks - * @description: 查看Volatile的字节码 - * @author: mf - * @create: 2020/02/12 02:58 - */ - -package com.juc.volatiletest; - -public class VolatileClassDemo { - volatile int n = 0; - - public void add() { - n++; - } - - public static void main(String[] args) { - - } -} diff --git a/Multithread/src/com/juc/volatiletest/VolatileOrder.java b/Multithread/src/com/juc/volatiletest/VolatileOrder.java deleted file mode 100644 index ba8f0777..00000000 --- a/Multithread/src/com/juc/volatiletest/VolatileOrder.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @program JavaBooks - * @description: volatile有序 - * @author: mf - * @create: 2020/02/13 01:13 - */ - -package com.juc.volatiletest; - -public class VolatileOrder { - // 单例 - private static VolatileOrder instance = null; - //private static volatile Singleton instance = null; - private VolatileOrder() { } - - public static VolatileOrder getInstance() { - //第一次判断 - if(instance == null) { - synchronized (VolatileOrder.class) { - if(instance == null) { - //初始化,并非原子操作 - instance = new VolatileOrder(); - } - } - } - return instance; - } -} diff --git a/Multithread/src/com/juc/volatiletest/VolatileVisibleDemo.java b/Multithread/src/com/juc/volatiletest/VolatileVisibleDemo.java deleted file mode 100644 index 08d4ded9..00000000 --- a/Multithread/src/com/juc/volatiletest/VolatileVisibleDemo.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @program JavaBooks - * @description: volatile的特性:可见性 - * @author: mf - * @create: 2020/02/13 00:49 - */ - -package com.juc.volatiletest; - -import java.util.concurrent.TimeUnit; - -public class VolatileVisibleDemo { - -// private boolean isReady = true; - private volatile boolean isReady = true; - - void m() { - System.out.println(Thread.currentThread().getName() + " m start..."); - while (isReady) { - } - System.out.println(Thread.currentThread().getName() + " m end..."); - } - - public static void main(String[] args) { - VolatileVisibleDemo demo = new VolatileVisibleDemo(); - new Thread(() -> demo.m(), "t1").start(); - try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} - demo.isReady = false; // 刚才一秒过后开始执行 - } -} diff --git "a/Multithread/\344\270\272\344\273\200\344\271\210\350\257\264ArrayList\346\230\257\347\272\277\347\250\213\344\270\215\345\256\211\345\205\250.md" "b/Multithread/\344\270\272\344\273\200\344\271\210\350\257\264ArrayList\346\230\257\347\272\277\347\250\213\344\270\215\345\256\211\345\205\250.md" deleted file mode 100644 index ef1a80cd..00000000 --- "a/Multithread/\344\270\272\344\273\200\344\271\210\350\257\264ArrayList\346\230\257\347\272\277\347\250\213\344\270\215\345\256\211\345\205\250.md" +++ /dev/null @@ -1,95 +0,0 @@ -## 引言 - -> 面试时相信面试官首先就会问到关于它的知识。一个经常被问到的问题就是:ArrayList是否是线程安全的?那么它为什么是线程不安全的呢?它线程不安全的具体体现又是怎样的呢?我们从源码的角度来看下。 - - - -## 源码分析 - -**首先看看该类的属性字段**: - -```java - /** - * 列表元素集合数组 - * 如果新建ArrayList对象时没有指定大小,那么会将EMPTY_ELEMENTDATA赋值给elementData, - * 并在第一次添加元素时,将列表容量设置为DEFAULT_CAPACITY - */ - transient Object[] elementData; - - /** - * 列表大小,elementData中存储的元素个数 - */ - private int size; -``` - -**ArrayList的实现主要就是用了一个Object的数组,用来保存所有的元素,以及一个size变量用来保存当前数组中已经添加了多少元素。** - -**再次看add方法的源码** - -```java -public boolean add(E e) { - - /** - * 添加一个元素时,做了如下两步操作 - * 1.判断列表的capacity容量是否足够,是否需要扩容 - * 2.真正将元素放在列表的元素数组里面 - */ - ensureCapacityInternal(size + 1); // Increments modCount!! - elementData[size++] = e; - return true; -} -``` - -**由此看到add元素时,实际做了两个大的步骤:** - -1. 判断elementData数组容量是否满足需求 -2. 在elementData对应位置上设置值 - -## 数组越界 - -1. 列表大小为9,即size=9 -2. 线程A开始进入add方法,这时它获取到size的值为9,调用ensureCapacityInternal方法进行容量判断。 -3. 线程B此时也进入add方法,它获取到size的值也为9,也开始调用ensureCapacityInternal方法。 -4. 线程A发现需求大小为10,而elementData的大小就为10,可以容纳。于是它不再扩容,返回。 -5. 线程B也发现需求大小为10,也可以容纳,返回。 -6. 线程A开始进行设置值操作, elementData[size++] = e 操作。此时size变为10。 -7. 线程B也开始进行设置值操作,它尝试设置elementData[10] = e,而elementData没有进行过扩容,它的下标最大为9。于是此时会报出一个数组越界的异常ArrayIndexOutOfBoundsException. - -## null值的情况 - -**elementData[size++] = e不是一个原子操作**: - -- elementData[size] = e; -- size = size + 1; - -**逻辑:** - -1. 列表大小为0,即size=0 -2. 线程A开始添加一个元素,值为A。此时它执行第一条操作,将A放在了elementData下标为0的位置上。 -3. 接着线程B刚好也要开始添加一个值为B的元素,且走到了第一步操作。此时线程B获取到size的值依然为0,于是它将B也放在了elementData下标为0的位置上。 -4. 线程A开始将size的值增加为1 -5. 线程B开始将size的值增加为2 - -**这样线程AB执行完毕后,理想中情况为size为2,elementData下标0的位置为A,下标1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成了B,下标1的位置上什么都没有。并且后续除非使用set方法修改此位置的值,否则将一直为null,因为size为2,添加元素时会从下标为2的位置上开始。** - -## 案例 - -```java - /** - * 故障现象 - * java.util.ConcurrentModificationException - */ - public static void notSafe() { - List list = new ArrayList<>(); - for (int i = 1; i <= 3; i++) { - new Thread(() -> { - list.add(UUID.randomUUID().toString().substring(0, 8)); - System.out.println(list); - }, "Thread " + i).start(); - } - } -``` - - - - diff --git "a/Multithread/\346\267\261\345\210\273\347\220\206\350\247\243volatile\347\232\204\344\270\200\345\210\207.md" "b/Multithread/\346\267\261\345\210\273\347\220\206\350\247\243volatile\347\232\204\344\270\200\345\210\207.md" deleted file mode 100644 index ceac1051..00000000 --- "a/Multithread/\346\267\261\345\210\273\347\220\206\350\247\243volatile\347\232\204\344\270\200\345\210\207.md" +++ /dev/null @@ -1,204 +0,0 @@ -## 引言 - -> 不得不说,如果谈到volatile只会它的作用:可见性,可序性和不能保证原子性,就太Low了些,因此还得熟悉其中的奥妙才行呀... - - - -## 可见性 - -**volatile的可见性是指当一个变量被volatile修饰后,这个变量就对所有线程均可见。白话点就是说当一个线程修改了一个volatile修饰的变量后,其他线程可以立刻得知这个变量的修改,拿到最这个变量最新的值。** - -```java -public class VolatileVisibleDemo { - -// private boolean isReady = true; - private volatile boolean isReady = true; - - void m() { - System.out.println(Thread.currentThread().getName() + " m start..."); - while (isReady) { - } - System.out.println(Thread.currentThread().getName() + " m end..."); - } - - public static void main(String[] args) { - VolatileVisibleDemo demo = new VolatileVisibleDemo(); - new Thread(() -> demo.m(), "t1").start(); - try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();} - demo.isReady = false; // 刚才一秒过后开始执行 - } -} -``` - -**分析:一开始isReady为true,m方法中的while会一直循环,而主线程开启开线程之后会延迟1s将isReady赋值为false,若不加volatile修饰,则程序一直在运行,若加了volatile修饰,则程序最后会输出t1 m end...** - -## 有序性 - -**有序性是指程序代码的执行是按照代码的实现顺序来按序执行的;volatile的有序性特性则是指禁止JVM指令重排优化。** - -```java -public class Singleton { - private static Singleton instance = null; - //private static volatile Singleton instance = null; - private Singleton() { } - - public static Singleton getInstance() { - //第一次判断 - if(instance == null) { - synchronized (Singleton.class) { - if(instance == null) { - //初始化,并非原子操作 - instance = new Singleton(); - } - } - } - return instance; - } -} -``` - -**上面的代码是一个很常见的单例模式实现方式,但是上述代码在多线程环境下是有问题的。为什么呢,问题出在instance对象的初始化上,因为`instance = new Singleton();`这个初始化操作并不是原子的,在JVM上会对应下面的几条指令:** - -```java -memory =allocate(); //1. 分配对象的内存空间 -ctorInstance(memory); //2. 初始化对象 -instance =memory; //3. 设置instance指向刚分配的内存地址 -``` - -**上面三个指令中,步骤2依赖步骤1,但是步骤3不依赖步骤2,所以JVM可能针对他们进行指令重拍序优化,重排后的指令如下:** - -```java -memory =allocate(); //1. 分配对象的内存空间 -instance =memory; //3. 设置instance指向刚分配的内存地址 -ctorInstance(memory); //2. 初始化对象 -``` - -**这样优化之后,内存的初始化被放到了instance分配内存地址的后面,这样的话当线程1执行步骤3这段赋值指令后,刚好有另外一个线程2进入getInstance方法判断instance不为null,这个时候线程2拿到的instance对应的内存其实还未初始化,这个时候拿去使用就会导致出错。** - -**所以我们在用这种方式实现单例模式时,会使用volatile关键字修饰instance变量,这是因为volatile关键字除了可以保证变量可见性之外,还具有防止指令重排序的作用。当用volatile修饰instance之后,JVM执行时就不会对上面提到的初始化指令进行重排序优化,这样也就不会出现多线程安全问题了。** - -## 不能保证原子性 - -**volatile关键字能保证变量的可见性和代码的有序性,但是不能保证变量的原子性,下面我再举一个volatile与原子性的例子:** - -```java -public class VolatileAtomicDemo { - public static volatile int count = 0; - - public static void increase() { - count++; - } - - public static void main(String[] args) { - Thread[] threads = new Thread[20]; - for(int i = 0; i < threads.length; i++) { - threads[i] = new Thread(() -> { - for(int j = 0; j < 1000; j++) { - increase(); - } - }); - threads[i].start(); - } - //等待所有累加线程结束 - while (Thread.activeCount() > 1) { - Thread.yield(); - } - System.out.println(count); - } -} -``` - -**上面这段代码创建了20个线程,每个线程对变量count进行1000次自增操作,如果这段代码并发正常的话,结果应该是20000,但实际运行过程中经常会出现小于20000的结果,因为count++这个自增操作不是原子操作。**[可参照这张图](https://www.processon.com/diagraming/5e130e50e4b0c090e0b7c183) - -## 内存屏障 - -**Java的Volatile的特征是任何读都能读到最新值,本质上是JVM通过内存屏障来实现的;为了实现volatile内存语义,JMM会分别限制重排序类型。下面是JMM针对编译器制定的volatile重排序规则表:** - -| 是否能重排序 | 第二个操作 | | | -| :----------: | :--------: | :--------: | :--------: | -| 第一个操作 | 普通读/写 | volatile读 | volatile写 | -| 普通读/写 | | | no | -| volatile读 | no | no | no | -| volatile写 | | no | no | - -**从上表我们可以看出:** - -- 当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。 -- 当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。 -- 当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。 - -**为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:** - -- 在每个volatile写操作的前面插入一个StoreStore屏障。 -- 在每个volatile写操作的后面插入一个StoreLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadLoad屏障。 -- 在每个volatile读操作的后面插入一个LoadStore屏障。 - -**volatile写插入内存指令图:** - -![volatile](https://segmentfault.com/img/bV7PaN?w=485&h=313) - -**上图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。** - -**这里比较有意思的是volatile写后面的StoreLoad屏障。这个屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面,是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确实现volatile的内存语义,JMM在这里采取了保守策略:在每个volatile写的后面或在每个volatile读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里我们可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率。** - -**volatile读插入内存指令图:** -![volatile读](https://segmentfault.com/img/bV7Pbg?w=500&h=306) - -**上图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。** - -**上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。下面我们通过具体的示例代码来说明:** - -```java -class VolatileBarrierExample { - int a; - volatile int v1 = 1; - volatile int v2 = 2; - - void readAndWrite() { - int i = v1; //第一个volatile读 - int j = v2; // 第二个volatile读 - a = i + j; //普通写 - v1 = i + 1; // 第一个volatile写 - v2 = j * 2; //第二个 volatile写 - } -} -``` - -**针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化:** - -![readAndWrite](https://segmentfault.com/img/bV7Pbl?w=491&h=465) - -**注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器常常会在这里插入一个StoreLoad屏障。** - -### volatile汇编: - -```java -0x000000011214bb49: mov %rdi,%rax - 0x000000011214bb4c: dec %eax - 0x000000011214bb4e: mov %eax,0x10(%rsi) - 0x000000011214bb51: lock addl $0x0,(%rsp) ;*putfield v1 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@21 (line 35) - - 0x000000011214bb56: imul %edi,%ebx - 0x000000011214bb59: mov %ebx,0x14(%rsi) - 0x000000011214bb5c: lock addl $0x0,(%rsp) ;*putfield v2 - ; - com.earnfish.VolatileBarrierExample::readAndWrite@28 (line 36) -``` - -**对应的Java:** - -```java -v1 = i - 1; // 第一个volatile写 -v2 = j * i; // 第二个volatile写 -``` - -**可见其本质是通过一个lock指令来实现的。那么lock是什么意思呢?** - -**它的作用是使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。所以通过这样一个空操作,可让前面volatile变量的修改对其他CPU立即可见。** - -- 锁住内存 -- 任何读必须在写完成之后再执行 -- 使其它线程这个值的栈缓存失效 - -[参考volatile内存屏障](https://segmentfault.com/a/1190000014315651?utm_source=tag-newest) \ No newline at end of file diff --git a/README.md b/README.md index a97852e3..d639f546 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,264 @@ -# JavaBooks +# java-notes + ## 引言 -> - [个人博客](http://dreamcat.ink/) -> - [在线阅读](http://dsystem.dreamcat.ink/#/) -> - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) - -## 常用网站 -- [:bookmark:开源项目总结](/Tools/network/开源的github项目总结.md) -- [:fire:常用的在线网站](/Tools/network/收集常用的网站(持续更新...).md) -- [:sparkles:emoji集合](/Tools/network/github表情图标.md) -- [:smiling_imp:Linux命令行的奇淫技巧](/Tools/network/Linux命令行的奇淫技巧.md) -- [📖今日热榜](https://tophub.today/):一款新闻聚合的产品,个人感觉还不错,闲时可以看一下新闻,可选择订阅哦 - -## 面经汇总(持续...) -> 个人建议:首先自己先有个知识点体系,不管是思维导图也好,还是大纲也好。 -> 那么其次看大量的面经,可以将每个面经当作一次面试,查看自己是否能讲出来,查漏补缺! -> 最后,不断沉淀即可。祝好运!!! - -- [美团所有问题汇总](/Interview/mianjing/meituan/美团所有问题汇总.md) -- [京东所有问题汇总](/Interview/mianjing/jingdong/京东所有问题汇总.md) -- [拼多多所有问题汇总](/Interview/mianjing/pinxx/拼多多所有问题汇总.md) -- [招银所有问题汇总](/Interview/mianjing/zhaoyin/招银所有问题汇总.md) -- [网易所有问题汇总](/Interview/mianjing/wangyi/网易所有问题汇总.md) -- [字节所有问题汇总](/Interview/mianjing/zijie/字节所有问题汇总.md) -- [远景所有问题汇总](/Interview/mianjing/yuanjing/远景所有问题汇总.md) -- [百度所有问题汇总](/Interview/mianjing/baidu/百度所有问题汇总.md) -- [腾讯所有问题汇总](/Interview/mianjing/tx/腾讯所有问题汇总.md) -- [阿里所有问题汇总](/Interview/mianjing/ali/阿里所有问题汇总.md) -- [猿辅导所有问题汇总](/Interview/mianjing/yuanfudao/猿辅导所有问题汇总.md) - -### 本人所经历的面经 -- [招银面经](/Interview/mianjing/zhaoyin/my.md) -- [京东面经](/Interview/mianjing/jingdong/my.md) -- [字节面经](/Interview/mianjing/zijie/my.md) -- [用友SP面经](/Interview/mianjing/yongyou/my.md) -- [百度面经](/Interview/mianjing/baidu/mmy.md) -- [网易面经](/Interview/mianjing/wangyi/my.md) -- [shein面经](/Interview/mianjing/shein/my.md) -- [贝壳面经](/Interview/mianjing/beike/my.md) -- [腾讯面经](/Interview/mianjing/tx/my.md) -- [作业帮面经](/Interview/mianjing/zuoyebang/my.md) -- [猿辅导面经](/Interview/mianjing/yuanfudao/my.md) - -## 我是这样回答的 -> 能力有限,但又想去钻研,面试中该怎么回答较好。(其中包含Java、数据库、计网、操作系统、分布式等持续总结...) -- [知识分类](/Interview/classify/README.md) - -## 刷题系列 -- [推荐:CS-Notes](https://cyc2018.github.io/CS-Notes/#/?id=✏️-算法) -> 个人建议,如果时间不充足的情况下,将CS-Notes的Leetcode刷明白,毕竟200道经典的题,也都很有套路,其次剑指offer,刷到5分钟自己能写完为止。 - -- [推荐:一写算法套路模版](https://github.com/labuladong/fucking-algorithm) -> 有些套路挺实用的,推荐阅读... - -- [手写多线程编程面试题](/Interview/myself/多线程编程题.md) -> 还在总结,但是都一样的套路... - -## 笔试题汇总(持续...) -- [LC-SQL](/Interview/mianjing/sql/sql.md) -- [图解SQL面试题](https://zhuanlan.zhihu.com/p/38354000) - -> 这里说一下, 图解SQL面试题,个人建议全部练习,也不难,但也的确面试高频手写SQL题 - -- [pdd](/Interview/bishi/pdd.md) -- [网易](/Interview/bishi/wangyi.md) -- [shopee](/Interview/bishi/shopee.md) -- [老虎](/Interview/bishi/laohu.md) -- [贝壳](/Interview/bishi/beike.md) - -> 可以从牛客找笔试题的感觉,也可以练习输入输出,做多了,你就发现基本没有树和链表! - -## Java面试思维导图(包括分布式架构) -- [知识架构思维导图](Interview/mind/README.md) - -## 吐血系列 -- [疯狂吐血系列](Interview/crazy/README.md) - -## 项目 -- [微服务班车在线预约系统](/Interview/bus/README.md) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 - -## 基础 -- [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) -- [Java面试基础知识](/Basics/Java面试基础知识.md) -- [Java面试基础知识](/Basics/Java面试基础常见问题.md) - -## 集合源码 -- [Java面经-Java集合框架](/Collections/Java面经-Java集合框架.md) -- [Java面经-ArrayList源码解析](/Collections/Java面经-ArrayList源码解析.md) -- [Java面经-LinkedList源码解析](/Collections/Java面经-LinkedList源码解析.md) -- [Java面经-HashSet-HashMap源码解析](/Collections/Java面经-HashSet-HashMap源码解析.md) -- [Java面经-LinkedHashSet-Map源码解析](/Collections/Java面经-LinkedHashSet-Map源码解析.md) -- [Java面经-TreeSet-TreeMap源码解析](/Collections/Java面经-TreeSet-TreeMap源码解析.md) -- [Java面经-PriorityQueue源码解析](/Collections/Java面经-PriorityQueue源码解析.md) -- [Java面经-Stack-Queue源码解析](/Collections/Java面经-Stack-Queue源码解析.md) -- [HashMap-ConcurrentHashMap面试必问](/Collections/HashMap-ConcurrentHashMap面试必问.md) -- [ArrayList源码图解](https://www.processon.com/view/link/5e13ddf5e4b07ae2d01c7369) -- [LinkedList源码图解](https://www.processon.com/view/link/5e13e641e4b0c090e0b88a59) -- [HashMap源码图解](https://www.processon.com/view/link/5e159150e4b07db4cfb0f418) - -## 多线程系列 -- [多线程思维导图](https://www.processon.com/view/link/5e4ab92de4b0996b2ba505bf) -- [Java多线程-并发基础常见面试题总结](/Multithread/Java多线程-并发基础常见面试题总结.md) -- [Java多线程-Synchronized](/Multithread/Java多线程-synchronized.md) -- [Java多线程-volatile](/Multithread/深刻理解volatile的一切.md) -- [Java多线程-CAS](/Multithread/CAS底层解析.md) -- [Java多线程-ThreadLocal](/Multithread/Java多线程-ThreadLocal.md) -- [Java多线程-Atomic原子类](/Multithread/Java多线程-Atomic原子类.md) -- [Java多线程-AQS](/Multithread/Java多线程-AQS.md) -- [Java多线程-线程池](/Multithread/Java多线程-线程池.md) -- [Java多线程-并发进阶常见面试题总结](/Multithread/Java多线程-并发进阶常见面试题总结.md) -- [多线程一些例子](/Multithread/README.md) -- [Java多线程常见问题](/Multithread/Java多线程常见问题.md) -- [谈谈Java内存模型图解](https://www.processon.com/view/link/5e129d57e4b0da16bb11d127) -- [有个成员变量int a = 1,那么a和1分别在jvm哪里图解](https://www.processon.com/view/link/5e13500de4b009af4a5fc40b) -- [线程的状态周期图](https://www.processon.com/view/link/5e16a379e4b0f5a7ed06d2fb) -- [volatile保证内存可见性和避免重排图](https://www.processon.com/view/link/5e12e591e4b061a80c683639) -- [volatile不能保证原子性操作图](https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c) -- [无锁-偏向锁-轻量级锁-重量级锁图](https://www.processon.com/view/link/5e1744a7e4b0f5a7ed086f4a) -- [内存屏障图](https://www.processon.com/view/link/5e4420bde4b06b291a6c463b) - -## JVM -- [JVM面试思维导图](https://www.processon.com/view/link/5e4c0704e4b00aefb7e74f44) -- [JVM-类文件结构](/Jvm/JVM-类文件结构.md) -- [JVM-类加载过程](/Jvm/JVM-类加载过程.md) -- [JVM-类加载机制](/Jvm/Java面经-类加载机制.md) -- [JVM-类加载器](/Jvm/JVM-类加载器.md) -- [JVM-内存模型](/Jvm/Java面经-内存模型.md) -- [JVM-对象创建](/Jvm/JVM-对象创建.md) -- [JVM-垃圾回收](/Jvm/JVM-垃圾回收.md) -- [JVM-调优参数](/Jvm/Java面经-JVM调优参数.md) -- [JVM面试常见问题](/Jvm/JVM面试常见问题.md) -- [JVM整个流程图](https://www.processon.com/view/link/5e1182afe4b009af4a5cc54d) - - -## 设计模式 -- [面试必问设计模式](/Interview/mode/README.md) +> - [个人博客](https://blog.heiye.site/) +> - [在线面试阅读](https://heiye.site/java-interview/) +> - **Dreamcats 的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) + +### 🔖DreamCats + +| 类型 | 名称 | 来源 | +| -------- | ---------------------------------------------------------------------------------------------------- | ---------- | +| 小程序 | [在线面试助手](https://github.com/DreamCats/online-interview) | DreamCats | +| 面经 | [大厂面经汇总](Java/mianjing/README.md) | DreamCats | +| 知识体系 | [Java 后端知识分类](Java/classify/README.md) | DreamCats | +| | [疯狂吐血系列](Java/crazy/README.md) | DreamCats | +| | [知识架构思维导图](Java/mind/README.md) | DreamCats | +| 刷题 | [CS-Notes](http://www.cyc2018.xyz/) | cyc | +| | [写算法套路模板](https://github.com/labuladong/fucking-algorithm) | labuladong | +| | [按热度总结 lc](Java/alg/按热度总结lc.md) | DreamCats | +| | [剑指 Offer](Java/alg/剑指offer.md) | DreamCats | +| | [个人秋招刷熟题](Java/alg/个人刷熟题.md) | DreamCats | +| | [秋招常考的热点题](Java/alg/README.md) | DreamCats | +| | [LC-SQL](Java/bishi/sql.md) | DreamCats | +| | [图解 SQL 面试题](https://zhuanlan.zhihu.com/p/38354000) | xxx | +| | [牛客走起来](https://www.nowcoder.com/contestRoom) | xxx | +| 项目 | [微服务班车在线预约系统-文档](Java/bus/README.md) | DreamCats | +| | [微服务班车在线预约系统-项目地址](https://github.com/DreamCats/school-bus) | DreamCats | +| | [B 站淘了 2 个 Java 实战项目:选一个认真钻研即可](https://mp.weixin.qq.com/s/B-Gzw20xKIPC_w4b_8bJiA) | JavaGuide | +| 其他 | [JDK1.8 部分源码](Java/jdk/README.md) | DreamCats | +| | [相应阅读的书籍](books.md) | DreamCats | +| | [Spring 简要学习](Java/spring-books/README.md) | DreamCats | +| | [在线推送 lc 脚本](https://github.com/DreamCats/dream-script/blob/master/notify_lc.py) | DreamCats | +| | [在线推送知识点](https://github.com/DreamCats/dream-script/blob/master/notify_know.py) | DreamCats | + +## 文章系列 + +> 有比较好的技术团队写的文章,值得大家阅读,受益匪浅。比如:阿里技术、美团技术团队等。 + +### 🔖 其他 + +| 类型 | 名称 | 来源 | +| ----- | ---------------------------------------------------------------------------------------- | --------------- | +| Other | [free-programming-books](https://github.com/EbookFoundation/free-programming-books) | EbookFoundation | +| Other | [计算机专业电子书下载](https://tanqingbo.cn/CSBook001/) | IT 码农 | +| Other | [计算机专业电子书下载-2](https://tanqingbo.cn/CSBook001/) | javaer-roadmap | +| Other | [各个技术开发文档](https://www.bookstack.cn/) | 书栈网 | +| Other | [TikTok 二面: 说下二维码登录的原理?](https://mp.weixin.qq.com/s/HUJxTbMr0mep9uxAOa4F0A) | 小哈学 | +| Other | [性能优化:关于缓存的一些思考](https://mp.weixin.qq.com/s/9aUTvdKFbsi_fzuO0BjPvw) | 阿里技术 | +| Other | [技术面试最后反问面试官的话](https://github.com/yifeikong/reverse-interview-zh) | | + +### 📉Java + +| 类型 | 题目 | 来源 | +| ------ | ---------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| Java | [面试官:谈谈 JVM 垃圾回收的三色标记,避坑建议看这篇就够了](https://www.toutiao.com/a6940243711880364577/) | Java 码农之路 | +| Java | [JVM 如何判断哪些对象可以回收?](https://mp.weixin.qq.com/s/4D49dPEfGlTV04vso3VT5Q) | 大鱼仙人 | +| Java | [Java Map 中那些巧妙的设计](https://mp.weixin.qq.com/s/7UTEHA6pdHeitg1htzdcRw) | 阿里技术团队 | +| Java | [JDK 16 中的 ZGC:平均暂停时间 0.05 毫秒](https://zhuanlan.zhihu.com/p/359249269?) | Glavo | +| Java | [再谈 synchronized 锁升级](https://mp.weixin.qq.com/s/G4z08HfiqJ4qm3th0KtovA) | 码农参上 | +| Java | [Java 线程池源码解析](https://juejin.cn/post/6946087172143317023) | Xiao 镔 | +| Java | [String 的不可变真的是因为 final 吗?](https://www.cnblogs.com/cswiki/p/14628286.html) | 飞天小牛肉 | +| Java | [假期后来一波干货:一文理清 JVM 和 GC](https://www.toutiao.com/a6947938522997342734/) | Java 架构师联盟 | +| Java | [**java**高并发情况下**sychronized**有什么问题?](http://mtw.so/6svoUV) | Jian | +| Java | [Java 并发高频面试题:聊聊你对 AQS 的理解?](http://mrw.so/6bwBMh) | 敖丙 | +| Java | [【基本功】不可不说的 Java“锁”事](https://mp.weixin.qq.com/s/E2fOUHOabm10k_EVugX08g) | 美团技术团队 | +| Java | [并发下的 HashMap 为什么会引起死循环???](http://mtw.so/5EG54Nhttp://mtw.so/5EG54N) | Java 学习者社区 | +| Java | [两次被裁之后,我终于解决了数据库缓存一致性问题](https://mp.weixin.qq.com/s/Ie2jGnRqv0vGgWvrKU0tMA) | 码农小说家 | +| Java | [别再纠结线程池大小/线程数量了,没有固定公式的](https://juejin.cn/post/6948034657321484318) | 空无 | +| Java | [Spring 拦截器和过滤器的区别?](http://mtw.so/5QnbxF) | 华为云开发者社区 | +| Java | [面试官:能说出 Synchronized 同步方法的八种使用场景吗](https://mp.weixin.qq.com/s/zTKcixJ6CUC-oq7VJMjYEA) | 我是程序汪 | +| Java | [Java 内存访问重排序的研究](https://tech.meituan.com/2014/09/23/java-memory-reordering.html) | 美团技术团队 | +| Java | [面试官:谈谈 JVM 垃圾回收的三色标记,避坑建议看这篇就够了](http://mrw.so/5suJqN) | Java 码农之路 | +| Java | [Java NIO 浅析](https://tech.meituan.com/2016/11/04/nio.html) | 美团技术团队 | +| Java | [Java Hotspot G1 GC 的一些关键技术](https://tech.meituan.com/2016/09/23/g1.html) | 美团技术团队 | +| Java | [Java 8 系列之重新认识 HashMap](https://tech.meituan.com/2016/06/24/java-hashmap.html) | 美团技术团队 | +| Java | [新一代垃圾回收器 ZGC 的探索与实践](https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html) | 美团技术团队 | +| Java | [Java 线程池实现原理及其在美团业务中的实践](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html) | 美团技术团队 | +| Spring | [盘点 SpringIOC : 循环依赖](https://juejin.cn/post/6953623068568715294) | 苦逼的程序源 | +| Java | [类加载常见错误总结,写得非常好!](https://mp.weixin.qq.com/s/tu90FNXKSQUID2l1dSZyJg) | Java 之道 | +| Java | [Java 双刃剑之 Unsafe 类详解](https://mp.weixin.qq.com/s/K5JrXsKVWoJ5JF3P95_P3w) | Dr Hydra | +| Java | [有了 CopyOnWrite 为何又要有 ReadWriteLock?](https://mp.weixin.qq.com/s/4-U7SpexOR331zVd3FnWuQ) | java 金融 | +| Java | [ThreadLocal 使用与原理](https://juejin.cn/post/6959333602748268575) | 敖丙 | +| Java | [动态代理总结,你要知道的都在这里,无废话!](https://juejin.cn/post/6963050105893027871) | 月伴飞鱼 | +| Java | [Java 并发-线程池篇-附场景分析](https://mp.weixin.qq.com/s/3uSBmQJcv9eEWs-mHl0Ifw) | 汤圆 | +| Java | [从源码角度学习 Java 动态代理](https://mp.weixin.qq.com/s/jaLvb21yVHg2R_gJ-JSeVA) | 盖伦专治花里胡哨 | +| Java | [现在已经卷到需要问三色标记了吗?](https://juejin.cn/post/6967184141833994248) | 艾小仙 | +| Java | [一文理解 JVM 线程属于用户态还是内核态](https://mp.weixin.qq.com/s/afr-S_hpuidUHDOSVxjjcw) | 全菜工程师小辉 | +| Java | [一文理解 Java 中的 SPI 机制](https://mp.weixin.qq.com/s/EvVSyJYtI2kNe7DNw-q38A) | 全菜工程师小辉 | +| Spring | [给老板解释解释,为什么要用 SpringCloud alibaba 作为微服务开发框架???](https://mp.weixin.qq.com/s/AYCSL5BhfOcH-HpE1VP-mg) | 票飘沙 Jam | +| Spring | [如何实现一个简易版的 Spring - 如何实现 AOP(终结篇)](https://www.cnblogs.com/mghio/p/14881156.html) | mghio | +| Java | [高频面试题-请把 Java 的双亲委派机制说清楚!](https://juejin.cn/post/6974405506630680590) | Ijiran | +| Spring | [北京某大公司:SpringBean 生命周期](https://juejin.cn/post/6979398918429736996) k | Java3y | +| Java | [通俗易懂的 ReentrantLock,不懂你来砍我](http://mtw.so/5x9ScQ) | 程序猿阿星 | +| Java | [面试官:"遇到过死锁问题吗?怎么发生的?如何解决呢?"](https://mp.weixin.qq.com/s/qSkRtwH_jUjXHPxu0KDQxA) | 承香墨影 | +| Java | [String s="a"+"b"+"c",到底创建了几个对象?](https://mp.weixin.qq.com/s/IVmVdCQCKOXk8NnHNcFz4A) | 码农参上 | +| Java | [聊聊 spring 事务失效的 12 种场景,太坑了](https://mp.weixin.qq.com/s/4M4rePjjy8-UBVvCzHchTQ) | 苏三说技术 | +| Java | [面渣逆袭:JVM 经典五十问,这下面试稳了!](https://mp.weixin.qq.com/s/XYsEJyIo46jXhHE1sOR_0Q) | 老三 | + +### 📚 数据库 + +| 类型 | 题目 | 来源 | +| ----- | ------------------------------------------------------------------------------------------------------------------ | ----------------- | +| MySQL | [分页场景(limit,offset)为什么会慢?](https://mp.weixin.qq.com/s/CfcU1rde3SkDlvCJGXnK7A) | Java 学习者社区 | +| MySQL | [谈谈 MySQL 锁](https://toutiao.io/k/z5c0vp0) | 郭儿的跋涉 | +| Redis | [好代码实践:基于 Redis 的轻量级分布式均衡消费队列](https://mp.weixin.qq.com/s/2cOhU3L88E3_ZV3ywl136A) | 阿里技术团队 | +| Redis | [Redis 入门:Redis 概念和基础 (查看原文)](https://toutiao.io/k/47fg3nq) | Java 全栈知识体系 | +| MySQL | [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html) | 美团技术团队 | +| MySQL | [MySQL 索引原理及慢查询优化](https://tech.meituan.com/2014/06/30/mysql-index.html) | 美团技术团队 | +| MySQL | [浅谈分库分表那些事儿](https://mp.weixin.qq.com/s/X6FI9Ci7ZXGDNDCkh2VnNA) | 阿里技术团队 | +| MySQL | [MySQL 与 Redis 缓存的同步方案](https://segmentfault.com/a/1190000039915710) | 民大哥 | +| MySQL | [我面试几乎必问:你设计索引的原则是什么?怎么避免索引失效?](https://www.cnblogs.com/hollischuang/p/14749826.html) | Hollischuang | +| MySQL | [分区取模分库分表策略:多表事务分库内闭环解决方案](https://mp.weixin.qq.com/s/4S_BhPtIMikefPJLo5498Q) | 阿里技术 | +| Redis | [亿级系统的 Redis 缓存如何设计???](https://mp.weixin.qq.com/s/mc1zzjy5fEbXCxwhJoWA2Q) | TomGE | +| MySQL | [美团二面:Redis 与 MySQL 双写一致性如何保证?](https://juejin.cn/post/6964531365643550751) | 捡田螺的小男孩 | +| MySQL | [索引失效的场景有哪些?索引何时会失效?](https://segmentfault.com/a/1190000040066409) | 民工哥 | +| MySQL | [看完这篇还不懂 MySQL 主从复制,可以回家躺平了~](https://www.cnblogs.com/qianyueric/p/14822012.html) | 小羽 | +| Redis | [快速了解缓存穿透与缓存雪崩](https://blog.csdn.net/y277an/article/details/97457358?spm=1001.2014.3001.5501) | 全菜工程师小辉 | +| MySQL | [面试题:mysql 一棵 B+ 树能存多少条数据?](https://mp.weixin.qq.com/s/IdpY7CPxyqRNx3BYYxl2Ow) | TomGE | +| MySQL | [为什么磁盘存储引擎用 b+树来作为索引结构?](https://mp.weixin.qq.com/s/8gDVqlywLBl-MZa6XrtXug) | 腾讯技术工程 | +| Redis | [源码级别了解 Redis 持久化](https://segmentfault.com/a/1190000040186654) | 蘑菇睡不着 | +| Redis | [大厂经典面试题:Redis 为什么这么快?](https://juejin.cn/post/6978280894704386079) | 捡田螺的小男孩 | +| MySQL | [一文了解数据拆分与分库分表](https://mp.weixin.qq.com/s/LDUWmEb8z5mTjPZLlJ1iVw) | 全菜工程师小辉 | +| Redis | [【redis 前传】zset 如何解决内部链表查找效率低下\|跳表构建](https://juejin.cn/post/6983810713055658015) | zxhtom | +| MySQL | [值得收藏,揭秘 MySQL 多版本并发控制实现原理](https://mp.weixin.qq.com/s/OYDfxgzNAOUGFILGk__CBQ) | 架构精进之路 | +| MySQL | [慢 SQL 排查思路?就这。](https://mp.weixin.qq.com/s/3yki4dljbLMgnOVrsqbk8w) | yes 的练级攻略 | +| MySQL | [42 张图带你撸完 MySQL 优化](https://www.cnblogs.com/cxuanBlog/p/15059928.html) | 程序员 emcxuan | +| MySQL | [容易引起雪崩的两个处理](http://mtw.so/6iZInk) | 编程一生 | +| MySQL | [一个 MySQL 锁和面试官大战三十回合,我霸中霸!](https://mp.weixin.qq.com/s/VjsQR_co2oM11EywegZrMw) | yes 的练级攻略 | +| MySQl | [五分钟搞懂 MySQL 索引下推](https://mp.weixin.qq.com/s/iQnArUNIsZFzVCj90MZjyg) | 老三 | +| Redis | [干货-16 张图吃透 Redis 架构演进](https://jishuin.proginn.com/p/763bfbd4d516) | IT | +| Java | [慢 sql 治理经典案例分享](https://mp.weixin.qq.com/s/WyfRV-7sK_O8pxDZbPXQtQ) | 阿里技术 | +| MySQL | [基于代价的慢查询优化建议](https://mp.weixin.qq.com/s/MaQTI4afIh2Zehc-F-iisQ) | 美团技术团队 | + +### 🧾 分布式&&中间件 + +| 类型 | 题目 | 来源 | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| 分布式 | [分布式系统互斥性与幂等性问题的分析与解决](https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html) | 美团技术团队 | +| 分布式锁 | [一文搞懂分布式锁的原理与实现](https://mp.weixin.qq.com/s/CiqQT4U3_NPbrBhGR6wAvQ) | 架构精进之路 | +| 并发 | [【万级并发】电商库存扣减如何设计?不超卖!](https://mp.weixin.qq.com/s/jJTIBL8unJ-IRbDqgREsCw) | TomGE | +| 分布式事务 | [七种分布式事务的解决方案,一次讲给你听](http://mrw.so/5WvneB) | moon 聊技术 | +| 分布式 | [服务端如何防止重复支付](https://juejin.cn/post/6956790589606068232) | Markerhub | +| 限流 | [为什么大厂服务并发高却很稳定?分布式服务熔断降级限流利器至 Hystrix](https://juejin.cn/post/6955201079017472014) | zxhtom | +| 分布式锁 | [SpringBoot + Redis 分布式锁:模拟抢单](https://mp.weixin.qq.com/s/g7NbrelsUhYg8pVO96Ui1g) | JAVA 小咖秀 | +| 消息队列 | [消息队列如何确保消息的有序性?](http://mtw.so/69rjv8) | 杜亦舒 | +| 消息队列 | [听叔一句劝,消息队列的水太深,你把握不住!](https://mp.weixin.qq.com/s/E5FWKPPhRBNhWaOiu2cXsQ) | 单一色调 | +| 分布式锁 | [丢,隔壁的老哥又让我用分布式锁](https://mp.weixin.qq.com/s/v7xpp9Iu8apgsSOf2xkyyA) | Java3y | +| Dubbo | [详解 Apache Dubbo 的 SPI 实现机制](http://mtw.so/6vZwy9) | Ning Peng | +| 分布式 | [携程最终一致和强一致性缓存实践](https://mp.weixin.qq.com/s/E-chAZyHtaZOdA19mW59-Q) | GSF | +| 分布式锁 | [基于 Redis 的分布式锁设计](https://www.cnblogs.com/xiaoxiaotank/p/14982602.html) | xiaoxiaotank | +| 分布式 | [分布式系统中一致性哈希算法](https://www.cnblogs.com/jajian/p/10896624.html) | 码辣架构 | +| 消息 | [消息幂等(去重)如何解决?来看看这个方案!](https://mp.weixin.qq.com/s/kLb1pweomL19aGaiHjO3Fg) | JAVA 日知录 | +| 本地缓存 | [Guava Cache 原理分析与最佳实践](https://mp.weixin.qq.com/s/ADcu_XKTJxXectMQ8S20SQ) | 梓川 | +| 分布式锁 | [万字长文说透分布式锁](https://mp.weixin.qq.com/s/35aCS_5GqLyzZS3VobL6fg) | 多颗糖 | +| 降级限流 | [高并发整体可用性:一文详解降级、限流和熔断](https://mp.weixin.qq.com/s/-E3RupBNVia0qfph-iOo1w) | Coder 的技术之路 | +| kafka | [Kafka 为什么要放弃 Zookeeper](https://mp.weixin.qq.com/s/0dHPu8O70v69mVuM4--HfA) | 捉虫大师 | + +### 🎈 数据结构 + +| 类型 | 题目 | 来源 | +| ---- | ------------------------------------------------------------------------------------ | ------------- | +| 算法 | [常见的初级排序算法,这次全搞懂](https://mp.weixin.qq.com/s/neol2vvmA_uXGbndKec1hw) | XSliently9527 | +| 算法 | [字节跳动最爱考的 64 道算法题(JS 版)](https://juejin.cn/post/6947842412102287373) | 图雀社区 | +| 算法 | [MySQL 索引底层:B+树详解](https://mp.weixin.qq.com/s/JqaN_1GPiqRQzXt2AizmdA) | Java3y | +| 算法 | [红黑树深入剖析及 Java 实现](https://tech.meituan.com/2016/12/02/redblack-tree.html) | 美团技术团队 | +| 算法 | [牛逼哄哄的 BitMap,到底牛逼在哪?](https://juejin.cn/post/6953821179836629005) | ITl 老哥 | +| 算法 | [布隆过滤器](https://www.wolai.com/dreamcat/iPRdAMVPcCJqiZ9gBd3Jvd?theme=dark) | DreamCats | +| 算法 | [实现 LRU 缓存机制](https://mp.weixin.qq.com/s/ZziTk_ZwHRRaIgorarJMhQ) | 郭儿的跋涉 | +| 算法 | [实现 LFU 缓存机制](https://mp.weixin.qq.com/s/Eyu1sKzrajttiQ0DxP5Q3w) | 郭儿的跋涉 | + +### 📖 计算机网络 + +| 类型 | 题目 | 来源 | +| ---- | --------------------------------------------------------------------------------------------------- | ---------- | +| 计网 | [GET 和 POST 请求的本质区别是什么?原来我一直理解错了](https://segmentfault.com/a/1190000039905721) | 码农突围 | +| 计网 | [懵了!有了 HTTP,为什么还要 RPC](http://mtw.so/6otjBG) | 小哈学 | +| 计网 | [“三次握手,四次挥手”这么讲,保证你忘不了](https://mp.weixin.qq.com/s/QxY2Y8BhGTaGgOG1dyRuFg) | 三分恶 | +| 计网 | [超详细 DNS 协议解析](https://juejin.cn/post/6919755385330991112) | 飞天小牛肉 | +| 计网 | [傻傻分不清之 Cookie、Session、Token、JWT](https://juejin.cn/post/6844904034181070861) | 秋天不落叶 | +| 计网 | [简略图解:输入 url 到出现页面,浏览器做了什么?](https://zhuanlan.zhihu.com/p/402559256) | 又拍云 | + +### ✉️ 操作系统 + +| 类型 | 题目 | 来源 | +| ---- | ----------------------------------------------------------------------------------------------- | -------------- | +| 系统 | [逛到底层看 NIO 的零拷贝](https://mp.weixin.qq.com/s/Oc787q5cK_83t5B6h631DQ) | 皇甫嗷嗷叫 | +| 系统 | [进程、线程与协程傻傻分不清?一文带你吃透!](https://mp.weixin.qq.com/s/jhOSjVyRA6rNKqVT2pKMIQ) | 程序猿阿星 | +| 系统 | [虚拟内存的那点事儿](https://juejin.cn/post/6844903507594575886) | Sylvanassun | +| 系统 | [今晚:**CPU** 核数与线程数有什么关系?](https://mp.weixin.qq.com/s/Tp680dfOB7Zb6xlXSay7XA) | 码农的荒岛求生 | +| 系统 | [谈谈操作系统的多进程](https://www.cnblogs.com/zhaozhibo/p/15109430.html) | ZhiboZhao | +| 系统 | [阿里二面:什么是 mmap?](https://mp.weixin.qq.com/s/czMlM6xuuyq2XlU_FB9aVQ) | 苏三说技术 | + +### 🍎 效率工具 + +| 类型 | 题目 | 来源 | +| ---- | ------------------------------------------------------------------------------------ | ---- | +| 写作 | [图床,uPic 和阿里 oss 搭建与使用](https://juejin.cn/post/7010985866185146399) | 司司 | +| Git | [你的文件需要 git,详细安装与使用(一)](https://juejin.cn/post/7011413073881727013) | 司司 | +| Git | [你的文件需要 git,常用命令(二)](https://tc.dreamcat.ink/archives/18.html) | 司司 | +| 文件 | [mac 解决移动文件的痛点-yoink](https://juejin.cn/post/7017382028865699877) | 司司 | +| 图床 | [管理图床-oss-browser ](https://juejin.cn/post/7026587086920613896/) | 司司 | +| 终端 | [时而花里胡哨,时而朴实无华-iterm2 ](https://juejin.cn/post/7026924031056019486/) | 司司 | +| rss | [及时推送-RSSHub](https://juejin.cn/post/7028022886380077093/) | 司司 | +| vim | [vim 编程-初探(一)](https://juejin.cn/post/7028887363266805773/) | 司司 | +| vim | [vim 编程-优雅(二)](https://juejin.cn/post/7033966288863133732/) | 黑夜 | + +### 😣 源码阅读 + +| 类型 | 题目 | 来源 | +| ----- | -------------------------------------------------------------------------------- | ---- | +| nginx | [闲不下来-nginx 是什么?(一)](https://juejin.cn/post/7012491394979725349) | 司司 | +| nginx | [闲不下来-nginx 环境搭建(二)](https://juejin.cn/post/7012922394478116900) | 司司 | +| nginx | [闲不下来-nginx 源码全局认知(三)](https://juejin.cn/post/7018372907780800526) | 司司 | +| nginx | [闲不下来-nginx 配置文件(四)](https://juejin.cn/user/2928754709248584) | 司司 | +| nginx | [闲不下来-nginx 基本数据结构(五)](https://juejin.cn/post/7025426746228867102/) | 司司 | +| nginx | [闲不下来-nginx 数组结构(六)](https://juejin.cn/post/7026253473184219150/) | 司司 | +| nginx | [闲不下来-nginx 链表结构(七)](https://juejin.cn/post/7031466605297008647/) | 司司 | +| nginx | [闲不下来-nginx 队列结构(八)](https://juejin.cn/post/7045147643555069988/) | 司司 | + +### 👽 我学设计模式 + +| 类型 | 题目 | 来源 | +| -------- | ------------------------------------------------------------------- | ---- | +| 设计模式 | [我学设计模式-单例模式](https://juejin.cn/post/7016615747073867784) | 司司 | +| 设计模式 | [我学设计模式-工厂模式](https://juejin.cn/post/7020608341969731591) | 司司 | + +### 💌 场景题 + +| 类型 | 题目 | 来源 | +| ---------- | ------------------------------------------------------------------------------------- | ---- | +| 数据结构 | [如何设计一个 map(一)](https://blog.heiye.site/article/5d07538f.html) | 黑夜 | +| 数据结构 | [如何设计一个并发 map(二)](https://blog.heiye.site/article/e46dcc51.html) | 黑夜 | +| 数据结构 | [如何设计一个 LRU(三)](https://blog.heiye.site/article/bb676e1b.html) | 黑夜 | +| 数据结构 | [如何设计一个布隆过滤器(四)](https://blog.heiye.site/article/4662309e.html) | 黑夜 | +| 系统设计 | [如何设计一个幂等方案(五)](https://blog.heiye.site/article/792e9ab.html) | 黑夜 | +| 锁 | [如何设计一个读写锁(六)](https://blog.heiye.site/article/cec40f04.html) | 黑夜 | +| 锁 | [如何设计一个分布式锁(七)](https://blog.heiye.site/article/aa3f1514.html) | 黑夜 | +| 锁 | [如何设计一个乐观锁(八)](https://blog.heiye.site/article/f57ca5bc.html) | 黑夜 | +| 登录 | [二维码扫描登录的原理(九)](https://blog.heiye.site/article/6f6c8f0a.html) | 黑夜 | +| 登录 | [手机验证码登录原理(十)](https://blog.heiye.site/article/b0bd063d.html) | 黑夜 | +| 登录 | [手机号码一键登录原理(十一)](https://blog.heiye.site/article/de27e5eb.html) | 黑夜 | +| 登录 | [app 手机号码登录区分新用户(十二)](https://blog.heiye.site/article/d3bfb199.html) | 黑夜 | +| 订单 | [订单过期自动更新状态(十三)](https://blog.heiye.site/article/f567d34f.html) | 黑夜 | +| 计数器 | [基于 Redis 计数器的实现(十四)](https://blog.heiye.site/article/5fc6d5d6.html) | 黑夜 | +| MySQL 分页 | [MySQL 分页查询优化(十五)](https://blog.heiye.site/article/121835cc.html) | 黑夜 | +| 限流算法 | [基于 Golang 实现的漏桶算法(十六)](https://blog.heiye.site/article/7c4f38da.html) | 黑夜 | +| 限流算法 | [基于 Golang 实现的令牌桶算法(十七)](https://blog.heiye.site/article/112f9fb6.html) | 黑夜 | +| 系统设计 | [如何设计拼单服务(十八)](https://blog.heiye.site/article/7bba2c37.html) | 黑夜 | +| 系统设计 | [如何设计短链服务(十九)](https://blog.heiye.site/article/7de561a6.html) | 黑夜 | +| 系统设计 | [如何设计视频弹幕服务(二十)](https://blog.heiye.site/article/9a691261.html) | 黑夜 | +| MySQL | [MySQL 并发事务写引发的问题(二十一)](https://blog.heiye.site/article/3e3b3d85.html) | 黑夜 | +| 热 key | [高并发热 key 的问题(二十二)](https://blog.heiye.site/article/d2e3e1b2.html) | 黑夜 | +| 库存 | [高并发情况下如何扣减库存(二十三) ](https://blog.heiye.site/article/6d6a84b3.html) | 黑夜 | + +## 🙈 与我联系 + +- 公众号(Dreamcats):沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。个人秋招经历、🐂 客面经问题按照频率总结、Java 一系列知识、数据库、分布式、微服务、前端、技术面试、每日文章等(持续更新) + +![wx-gzh](http://imgs.heiye.site/blog/wxgzh.jpg) + +- 小程序(在线面试助手):包含各个大厂的面经、算法题、知识点,并且小程序包含长按复制和点击图片预览功能,同时小程序继续优化和开发。 +![wx-xcx](http://imgs.heiye.site/blog/online-interview-qr.jpg) diff --git a/SwordOffer/.DS_Store b/SwordOffer/.DS_Store deleted file mode 100644 index 42e90748..00000000 Binary files a/SwordOffer/.DS_Store and /dev/null differ diff --git a/SwordOffer/README.md b/SwordOffer/README.md deleted file mode 100644 index bc1b1e4e..00000000 --- a/SwordOffer/README.md +++ /dev/null @@ -1,136 +0,0 @@ -## 剑指offer(书的顺序) -- [2、单例模式](/SwordOffer/src/books/T2.java) -- [3、数组中重复的数字](/SwordOffer/src/books/T3.java) -- [4、二维数组中的查找](/SwordOffer/src/books/T4.java) -- [5、替换空格](/SwordOffer/src/books/T5.java) -- [6、从尾到头打印链表](/SwordOffer/src/books/T6.java) -- [7、重建二叉树](/SwordOffer/src/books/T7.java) -- [9、用两个栈实现一个队列](/SwordOffer/src/books/T9.java) -- [10、斐波那契数列 & 青蛙跳台阶](/SwordOffer/src/books/T10.java) -- [11、旋转数组的最小数字](/SwordOffer/src/books/T11.java) -- [12、矩阵中的路径](/SwordOffer/src/books/T12.java) -- [13、机器人的运动范围](/SwordOffer/src/books/T13.java) -- [14、剪绳子](/SwordOffer/src/books/T14.java) -- [15、二进制中1的个数](/SwordOffer/src/books/T15.java) -- [16、数值的整数次方](/SwordOffer/src/books/T16.java) -- [17、打印从1到最大的n位数](/SwordOffer/src/books/T17.java) -- [18、删除链表中的节点O(1)](/SwordOffer/src/books/T18.java) -- [19、正则表达式](/SwordOffer/src/books/T19.java) -- [20、表示数值的字符串](/SwordOffer/src/books/T20.java) -- [21、调整数组顺序使奇数位于偶数前面](/SwordOffer/src/books/T21.java) -- [22、链表中倒数第k个节点](/SwordOffer/src/books/T22.java) -- [23、链表中环的入口节点](/SwordOffer/src/books/T23.java) -- [24、反转链表](/SwordOffer/src/books/T24.java) -- [25、合并两个排序的链表](/SwordOffer/src/books/T25.java) -- [26、树的子结构](/SwordOffer/src/books/T26.java) -- [27、树的镜像](/SwordOffer/src/books/T27.java) -- [28、对称的二叉树](/SwordOffer/src/books/T28.java) -- [29、顺时针打印矩阵](/SwordOffer/src/books/T29.java) -- [30、包含min函数的栈](/SwordOffer/src/books/T30.java) -- [31、栈的压入、弹出序列](/SwordOffer/src/books/T31.java) -- [32、从上到下打印二叉树](/SwordOffer/src/books/T32.java) -- [33、二叉搜索树的后序遍历序列](/SwordOffer/src/books/T33.java) -- [34、二叉树中和为某一值的路径](/SwordOffer/src/books/T34.java) -- [35、复杂链表的复制](/SwordOffer/src/books/T35.java) -- [36、二叉搜索树与双向链表](/SwordOffer/src/books/T36.java) -- [37、序列化二叉树](/SwordOffer/src/books/T37.java) -- [38、字符串的排列](/SwordOffer/src/books/T38.java) -- [39、数组中出现次数超过一半的数字](/SwordOffer/src/books/T39.java) -- [40、最小的k个数](/SwordOffer/src/books/T40.java) -- [41、数据流中的中位数](/SwordOffer/src/books/T41.java) -- [42、连续子数组的最大和](/SwordOffer/src/books/T42.java) -- [43、1~n整数中1出现的次数](/SwordOffer/src/books/T43.java) -- [44、数字序列中某一位的数字](/SwordOffer/src/books/T44.java) -- [45、把数组排成最小的数](/SwordOffer/src/books/T45.java) -- [46、把数字翻译成字符串](/SwordOffer/src/books/T46.java) -- [47、礼物的最大价值](/SwordOffer/src/books/T47.java) -- [48、最长不含重复字符的子字符串](/SwordOffer/src/books/T48.java) -- [49、丑数](/SwordOffer/src/books/T49.java) -- [50、第一个只出现一次的字符](/SwordOffer/src/books/T50.java) -- [51、数组中的逆序对](/SwordOffer/src/books/T51.java) -- [52、两个链表的第一个公共节点](/SwordOffer/src/books/T52.java) -- [53、在排序数组中查找数字](/SwordOffer/src/books/T53.java) -- [54、二叉搜索树的第K大节点](/SwordOffer/src/books/T54.java) -- [55、二叉树的深度](/SwordOffer/src/books/T55.java) -- [56、数组中只出现一次的两个数字](/SwordOffer/src/books/T56.java) -- [57、和为s的数字](/SwordOffer/src/books/T57.java) -- [58、翻转字符串](/SwordOffer/src/books/T58.java) -- [59、滑动窗口的最大值](/SwordOffer/src/books/T59.java) -- [60、n个骰子的点数](/SwordOffer/src/books/T60.java) -- [61、扑克牌中的顺子](/SwordOffer/src/books/T61.java) -- [62、圆圈中最后剩下的数字](/SwordOffer/src/books/T62.java) -- [63、股票的最大利润](/SwordOffer/src/books/T63.java) -- [64、求1+2+...+n](/SwordOffer/src/books/T64.java) -- [65、不用加减乘除做加法](/SwordOffer/src/books/T65.java) -- [66、构建乘积数组](/SwordOffer/src/books/T66.java) - -## 牛客网的顺序 -| 序号 | 题目 | 语言 | 类型 | -| :--------------------------------------: | :--------------------------: | ---------------------------------------- | ---------------------------------------- | -| 1 | [二维数组中的查找](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) | [java](/SwordOffer/src/web/T1.java) | 数组 | -| 2 | [替换空格](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) | [java](/SwordOffer/src/web/T2.java) | 字符串 | -| 3 | [从尾到头打印链表](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T3.java) | 链表 | -| 4 | [重建二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T4.java) | 树 | -| 5 | [用两个栈实现一个队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T5.java) | 栈队列 | -| 6 | [旋转数组的最小数字](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T6.java) | 查找 | -| 7 | [斐波那契数列](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T7.java) | 递归 | -| 8 | [跳台阶](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T8.java) | 递归 | -| 9 | [变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T9.java)) | 贪心 | -| 10 | [矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T10.java) | 递归 | -| 11 | [二进制1的个数](https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&tqId=11164&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T11.java) | 二进制 | -| 12 | [数值的整数次方](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T12.java) |数学 | -| 13 | [调整数组顺序使奇数位于偶数前面](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T13.java) | 数组 | -| 14 | [链表中倒数第k个结点](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T14.java) | 链表 | -| 15 | [反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T15.java) | 链表 | -| 16 | [合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T16.java) | 链表 | -| 17 | [树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T17.java) | 树 | -| 18 | [二叉树的镜像](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T18.java) | 树 | -| 19 | [顺时针打印矩阵](/https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T19.java) | 数组 | -| 20 | [包含min函数的栈](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T20.java) | 栈 | -| 21 | [栈的压入、弹出序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T21.java) | 栈 | -| 22 | [从上往下打印二叉树](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T22.java) | 队列/树 | -| 23 | [二叉搜索树的后序遍历序列](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T23.java) | 树 | -| 24 | [二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T24.java) | 树 | -| 25 | [复杂链表的复制](https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T25.java) | 链表 | -| 26 | [二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T26.java) | 链表/树 | -| 27 | [字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T27.java) | 字符串 | -| 28 | [数组中出现次数超过一半的数字](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T28.java) | 数组 | -| 29 | [最小的k个数](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T29.java) | 数组 | -| 30 | [连续子数组的最大和](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T30.java) | 数组 | -| 31 | [整数中1出现的次数](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T31.java) | 查找 | -| 32 | [把数组排成最小的数](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T32.java) | 数组 | -| 33 | [丑数](https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T33.java) | 穷举 | -| 34 | [第一个只出现一次的字符](https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T34.java) | | -| 35 | [数组中的逆序对](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T35.java) | 数组 | -| 36 | [两个链表的第一个公共结点](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T36.java) | 链表 | -| 37 | [数字在排序数组中出现的次数](https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T37.java) | 数组 | -| 38 | [二叉树的深度](https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T38.java) | 树 | -| 39 | [平衡二叉树](https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T39.java) | 树 | -| 40 | [数组中只出现一次的数字](https://www.nowcoder.com/practice/e02fdb54d7524710a7d664d082bb7811?tpId=13&tqId=11193&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T40.java) | 数组 | -| 41 | [和为S的连续正数序列](https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&tqId=11194&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T41.java) | 数组 | -| 42 | [和为S的两个数字](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&tqId=11195&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T42.java) | 数组 | -| 43 | [左旋转字符串](https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T43.java) | 字符串 | -| 44 | [翻转单词顺序列](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T44.java) | 字符串 | -| 45 | [扑克牌顺序](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T45.java) | 字符串 | -| 46 | [孩子们的游戏](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T46.java) | 数学 | -| 47 | [求1+2+3+...+n](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T47.java) | 进制转换 | -| 48 | [不用加减乘除做加法](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T48.java) | 进制转换 | -| 49 | [把字符串转成整数](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T49.java) | 字符串 | -| 50 | [数组中重复的数字](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T50.java) | 数组 | -| 51 | [构建乘积数组](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T51.java) | 数组 | -| 52 | [正则表达式匹配](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T52.java) | 字符串 | -| 53 | [表示数值的字符串](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T53.java) | 字符串 | -| 54 | [字符流中第一个不重复的字符](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T54.java) | 字符串 | -| 55 | [链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T55.java) | 链表 | -| 56 | [删除链表中重复的结点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T56.java) | 链表 | -| 57 | [二叉树的下一个结点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T57.java) | 树 | -| 58 | [对称的二叉树](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T58.java) | 树 | -| 59 | [按之字形顺序打印二叉树](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T59.java) | 栈树 | -| 60 | [把二叉树打印多行](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T60.java) | 队列树 | -| 61 | [序列化二叉树](nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T61.java) | 队列树 | -| 62 | [二叉搜索树的第k个结点](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T62.java) | 栈树 | -| 63 | [数据流中的中位数](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T63.java) | 进制转换 | -| 64 | [滑动窗口的最大值](nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T64.java) | 数组 | -| 65 | [矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T65.java) | 数组 | -| 66 | [机器人的运动范围](https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T66.java) | 数组 | -| 67 | [剪绳子](https://www.nowcoder.com/practice/57d85990ba5b440ab888fc72b0751bf8?tpId=13&tqId=33257&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) | [java](/SwordOffer/src/web/T67.java) | 贪心 | diff --git a/SwordOffer/src/books/ListNode.java b/SwordOffer/src/books/ListNode.java deleted file mode 100644 index a7c9d780..00000000 --- a/SwordOffer/src/books/ListNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 链表 - * @author: mf - * @create: 2019/08/30 09:51 - */ - -public class ListNode { - int value; - ListNode next; - ListNode random; - public ListNode(int value) { - this.value = value; - } -} diff --git a/SwordOffer/src/books/T10.java b/SwordOffer/src/books/T10.java deleted file mode 100644 index b1425b89..00000000 --- a/SwordOffer/src/books/T10.java +++ /dev/null @@ -1,103 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 青蛙跳台阶问题 & 斐波那契数列 - * @author: mf - * @create: 2019/08/22 15:25 - */ - -/* -求斐波那契数列的第n项 -写一个函数,输入n,求斐波那契数列的第n项, -斐波那契数列的定义如下: - 0 n=0 -f(n)= 1 n=1 - f(n-1)+f(n-2) n>1 - */ - - - - -/* -一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶 -求该青蛙跳上一个n级的台阶总共有多少种跳法。 - */ -public class T10 { - public static void main(String[] args) { - int res = frog(5); - System.out.println(res); - int res1 = frog2(5); - System.out.println(res1); - - System.out.println(fibonacci2(10)); - } - - - /* - 青蛙 - */ - // 递归 - public static int frog(int n) { - if (n == 0) return 0; - if (n == 1) return 1; - if (n == 2) return 2; - return frog(n - 1) + frog(n - 2); - } - - // 从下往上for循环 - public static int frog2(int n) { - int[] res = {0 , 1, 2}; - if (n < 3) return res[n]; - - int one = 1; - int two = 2; - int sum = 0; - for (int i = 3; i <= n; i++) { - sum = one + two; - one = two; - two = sum; - } - return sum; - } - - /* - 变态青蛙 - */ - // 递归 2*(n - 1) 次方 - public static int btFrog(int n) { - if (n == 1 || n == 2) return n; - return 2 * frog(n - 1); - } - // 2*(n - 1) 次方 - public static int btFrog2(int n) { - return 1<<(n - 1); - } - - /* - 斐波那契数列 - */ - - // 递归 缺点太慢 - public static int fibonacci(int n) { - if (n <= 0) return 0; - if (n == 1) return 1; - return fibonacci(n - 1) + fibonacci(n - 2); - } - - // 从下向上循环 - public static int fibonacci2(int n) { - int[] result = {0, 1}; - if (n < 2) return result[n]; - - int fiOne = 0; - int fiTwo = 1; - int fiRes = 0; - for (int i = 2; i <= n; i++) { - fiRes = fiOne + fiTwo; - fiOne = fiTwo; - fiTwo = fiRes; - } - return fiRes; - } -} diff --git a/SwordOffer/src/books/T11.java b/SwordOffer/src/books/T11.java deleted file mode 100644 index 1ec76812..00000000 --- a/SwordOffer/src/books/T11.java +++ /dev/null @@ -1,81 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 旋转数组的最小数字 - * @author: mf - * @create: 2019/08/23 10:18 - */ - -/* -把一个数组最开始的若干个元素搬到数组的末尾,我们称之 -为数组的旋转。输入一个递增排序的数组是的一个旋转,输出旋转 -数组的最小元素。例如{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的 -一个旋转,该数组的最小值为1。 -{0, 1, 1, 1, 1, ,1, 1} - */ -public class T11 { - public static void main(String[] args) { - int[] arr = {3, 4, 5, 1, 2}; - System.out.println(findMin2(arr)); - } - - /** - * - * @param arr - * @return 坐标 - */ - public static int findMin(int[] arr) { - int p1 = 0; - int p2 = arr.length - 1; - int mid = p1; - - while (arr[p1] >= arr[p2]) { - if (p2 - p1 == 1) { - mid = p2; - break; - } - mid = p1 + ((p2 - p1) >> 1); - // 提高代码健壮性,若是出现arr[p1] arr[p2] mid三者相等 - if (arr[p1] == arr[p2] && arr[p1] == mid) { - return ergodic(arr, p1, p2); - } - if (arr[p1] < arr[mid]) { - p1 = mid; - } else { - p2 = mid; - } - - } - return mid; - } - - public static int ergodic(int[] arr, int p1, int p2) { - int res = 0; - for (int i = p1; i <= p2; i++) { - if (res > arr[i]) { - res = arr[i]; - } - } - return res; - } - - /** - * 分析数组规律,单指针即可 - * @param arr - * @return - */ - public static int findMin2(int[] arr) { - if (arr.length == 0) return 0; - if (arr.length == 1) return arr[0]; - int a = arr[0]; - for (int i = 1; i < arr.length; i++) { - if (a > arr[i]) { - return arr[i]; - } else { - a = arr[i]; - } - } - return 0; - } -} diff --git a/SwordOffer/src/books/T12.java b/SwordOffer/src/books/T12.java deleted file mode 100644 index 8dd03fed..00000000 --- a/SwordOffer/src/books/T12.java +++ /dev/null @@ -1,67 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 矩阵中的路径 - * @author: mf - * @create: 2019/08/24 15:02 - */ - -/* -请设计一个函数,用来判断在一个矩阵中是否存在一条包含某 -字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步 -可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵 -的某一格,那么该路径不能再次进入该格子。例如,在下面的3x4的矩阵中包含一条 -字符串"afce"的路径(路径中的字母用下画线标出。)但矩阵中不包含字符串"abfb"的 -路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次 -进入这个格子 - */ -public class T12 { - public static void main(String[] args) { - char[][] arr = { - {'a', 'b', 't', 'g'}, - {'c', 'f', 'c', 's'}, - {'j', 'd', 'e', 'h'}}; - char[] s = {'b', 'f', 'c', 'e', '\0'}; - System.out.println(hasPath(arr, s)); - - } - - public static boolean hasPath(char[][] arr, char[] s) { - if (arr == null || arr.length < 1 || arr[0].length < 1) return false; - int rowNum = arr.length; - int colNum = arr[0].length; - int pathNum = 0; - boolean[][] visited = new boolean[rowNum][colNum]; - for (int i = 0; i < rowNum; i++) { - for (int j = 0; j < colNum; j++) { - if (hasPathCore(arr, rowNum, colNum, i, j, s, pathNum, visited)) { - return true; - } - } - } - return false; - } - - public static boolean hasPathCore(char[][] arr, int rowNum, int colNum, int i, int j, char[] s, int pathNum, boolean[][] visited) { - if (s[pathNum] == '\0') { - return true; - } - - boolean hasPath = false; - if (i >= 0 && i < rowNum && j >= 0 && j < colNum && arr[i][j] == s[pathNum] && !visited[i][j]){ - pathNum++; - visited[i][j] = true; - hasPath = hasPathCore(arr, rowNum, colNum, i, j -1, s, pathNum, visited) - ||hasPathCore(arr, rowNum, colNum, i - 1, j, s, pathNum, visited) - ||hasPathCore(arr, rowNum, colNum, i, j + 1, s, pathNum, visited) - ||hasPathCore(arr, rowNum, colNum, i + 1, j, s, pathNum, visited); - if (!hasPath){ - pathNum--; - visited[i][j] = false; - } - } - return hasPath; - } - -} diff --git a/SwordOffer/src/books/T13.java b/SwordOffer/src/books/T13.java deleted file mode 100644 index b7b36153..00000000 --- a/SwordOffer/src/books/T13.java +++ /dev/null @@ -1,64 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 机器人的运动范围 - * @author: mf - * @create: 2019/08/25 09:45 - */ - -/* -地上有一个m行n列的方格。一个机器人从坐标(0,0)的格子开始移动 -它每次可以向左、右、上、下移动一格,但不能进入行坐标和列坐标 -的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格(35, 37), 因为 -3+5+3+7=18。但它不能进入方格(35, 38),因为3+5+3+8=19。请问? -该机器人能够到达多少个格子。 - */ -public class T13 { - public static void main(String[] args) { - - int count = movingCount(18, 40, 40); - System.out.println(count); - } - - public static int movingCount(int threshold, int rows, int cols) { - if (threshold < 0 || rows < 1 || cols < 0) return 0; - boolean[][] visited = new boolean[rows][cols]; - int count = movingCountCore(threshold, rows, cols, 0, 0, visited); - return count; - - } - - public static int movingCountCore(int threshold, int rows, int cols, int i, int j, boolean[][] visited) { - int count = 0; - if (check(threshold, rows, cols, i, j, visited)) { - visited[i][j] = true; - // 核心之二 - count = 1 + movingCountCore(threshold, rows, cols, i, j - 1, visited) - + movingCountCore(threshold, rows, cols, i, j + 1, visited) - + movingCountCore(threshold, rows, cols, i - 1, j, visited) - + movingCountCore(threshold, rows, cols, i + 1, j, visited); - } - return count; - } - - public static boolean check(int threshold, int rows, int cols, int i, int j, boolean[][] visited) { - // 核心之一 - if (i >=0 && j >= 0 && i < rows && j < cols - && getDigiSum(i) + getDigiSum(j) <= threshold - && !visited[i][j]) return true; - return false; - } - - - // 常用方法,求一个数的总和 - public static int getDigiSum(int i) { - int sum = 0; - while (i > 0 ) { - sum += i % 10; - i /= 10; - } - - return sum; - } -} diff --git a/SwordOffer/src/books/T14.java b/SwordOffer/src/books/T14.java deleted file mode 100644 index 7e148025..00000000 --- a/SwordOffer/src/books/T14.java +++ /dev/null @@ -1,61 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 剪绳子 - * @author: mf - * @create: 2019/08/25 15:55 - */ - -/* -给你一根长度为n的绳子,请把绳子剪成m段(m,n都是整数,n>1并且m>1), -每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能 -的最大值乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别2、3、3的三段, -此时得到的最大乘积是18 - */ -public class T14 { - public static void main(String[] args) { - int max = maxProductAfterCutting1(4); - System.out.println(max); - int max1 = maxProductAfterCutting2(4); - System.out.println(max1); - } - - // 动态规划 - private static int maxProductAfterCutting1(int length) { - if (length < 2) return 0; - if (length == 2) return 1; - if (length == 3) return 2; - int[] products = new int[length + 1]; - products[0] = 0; - products[1] = 1; // 长度为2... - products[2] = 2; // 长度为3... - products[3] = 3; // 长度为4... - - int max = 0; - for (int i = 4; i <= length; i++) { - max = 0; - for (int j = 1; j <= i / 2; j++) { - int product = products[j] * products[i - j]; - max = max > product ? max : product; - products[i] = max; - } - } - max = products[length]; - - return max; - } - - // 贪婪算法 - public static int maxProductAfterCutting2(int length) { - if (length < 2) return 0; - if (length == 2) return 1; - if (length == 3) return 2; - // 尽可能剪3 - int timesOf3 = length / 3; - // 如果==1的话, 我就变为最后剩4 ,然后变2x2 - if (length - timesOf3 * 3 == 1) timesOf3 -= 1; - int timeOfs2 = (length - timesOf3 * 3) / 2; - return (int)(Math.pow(3, timesOf3)) * (int)(Math.pow(2, timeOfs2)); - } -} diff --git a/SwordOffer/src/books/T15.java b/SwordOffer/src/books/T15.java deleted file mode 100644 index b7d6e026..00000000 --- a/SwordOffer/src/books/T15.java +++ /dev/null @@ -1,56 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二进制中1的个数 - * @author: mf - * @create: 2019/08/26 10:04 - */ - -/* -请实现一个函数,输入一个整数,输出该整数二进制表示中1的个数。 -例如,把9表示成二进制是1001,有2位是1。因此,如果输入9,则该函数 -输出2。 - */ -public class T15 { - public static void main(String[] args) { - System.out.println(NumberOf1(9)); - System.out.println(NumberOf2(9)); - System.out.println(NumberOf3(9)); - } - - // 最高效的方法 - private static int NumberOf3(int n) { - int count = 0; - while (n != 0) { - count++; - n = (n - 1) & n; - } - return count; - } - - - // 出现负数就凉了 - private static int NumberOf1(int num) { - int count = 0 ; - while(num != 0) { - if ((1 & num) == 1) { - count++; - } - num = num >> 1; - } - return count; - } - // 但是循环32次,慢 - private static int NumberOf2(int num) { - int count = 0; - int flag = 1; - while(flag >= 1) { - if ((num & flag) >= 1) - count++; - - flag = flag << 1; - } - return count; - } -} diff --git a/SwordOffer/src/books/T16.java b/SwordOffer/src/books/T16.java deleted file mode 100644 index 66243576..00000000 --- a/SwordOffer/src/books/T16.java +++ /dev/null @@ -1,53 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 数值的整数次方 - * @author: mf - * @create: 2019/08/27 09:49 - */ - -public class T16 { - public static void main(String[] args) { - double value = doublePow(2.0, 8); - System.out.println(value); - } - // 0的0次方没有意义, 所以有个条件限制 - private static double doublePow(double number, int exp) { - double result = 1.0; - if (number == 0.0 && exp < 0) return 0.0; - boolean expSign = true; - if (exp < 0) { - expSign = false; - exp = - exp; - } -// result = powerUnsignExp(number, exp); - result = powerUnsignExp2(number, exp); - if (!expSign) { - result = 1 / result; - } - return result; - } - // 效率较低 - private static double powerUnsignExp(double number, int exp) { - double result = 1.0; - for (int i = 1; i <= exp; i++) { - result *= number; - } - return result; - } - // 高效率 递归 - private static double powerUnsignExp2(double number, int exp) { - if (exp == 0) return 1; - if (exp == 1) return number; // 不管是奇数还是偶数,都会将exp递归到1 都会到这里返回 - // 讲究细节 - double result = powerUnsignExp2(number, exp >> 1); - result *= result; - // 讲究细节 奇数 - if ((exp & 0x1) == 1) result *= number; - - return result; - - } - -} diff --git a/SwordOffer/src/books/T17.java b/SwordOffer/src/books/T17.java deleted file mode 100644 index 2c62ff73..00000000 --- a/SwordOffer/src/books/T17.java +++ /dev/null @@ -1,107 +0,0 @@ -package books; - -import java.util.Arrays; - -/** - * @program JavaBooks - * @description: 打印从1到最大的n位数 - * @author: mf - * @create: 2019/08/28 09:51 - */ -/* -输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3 -则打印出1、2、3一直到最大的3位数999 - */ - -/* -面试官没给n的范围, 万一很大呢? int和long岂不是要溢出? -这种题溢出的题,字符串可以搞定。 - */ -public class T17 { - public static void main(String[] args) { -// printToMax(2); -// printToMax2(2); - printToMax3(2); - } - // 最笨的方法,一定不符合面试官的要求 - public static void printToMax(int n) { - int number = 1; - // 先求最大数 - while (n-- > 0) { - number *= 10; - } - - while (number-- > 1) { - System.out.println(number); - } - } - // - public static void printToMax2(int n) { - if (n <= 0) return; - char[] number = new char[n]; - for (int i = 0; i < number.length; i++) { - number[i] = '0'; - } - while (!Increment(number)) { - printNumber(number); - } - } - - - - private static boolean Increment(char[] number) { - boolean isOverflow = false; - int nTakeOver = 0; - int nLength = number.length; - for (int i = nLength - 1; i >= 0; i--) { - int nSum = number[i] - '0' + nTakeOver; - if (i == nLength - 1) nSum++; - if (nSum >= 10) { - if (i == 0) isOverflow = true; - else { - nSum -= 10; - nTakeOver = 1; - number[i] = (char) ('0' + nSum); - } - } else { - number[i] = (char) ('0' + nSum); - System.out.println(number[i]); - break; - } - - } - return isOverflow; - } - private static void printNumber(char[] number) { - boolean isBeginning0 = true; - - int nLength = number.length; - for (int i = 0; i < nLength; i++) { - if (isBeginning0 && number[i]!= '0') isBeginning0 = false; - if(! isBeginning0) System.out.print(number[i]); - } - System.out.print('\t'); - } - - - // 递归方法,把递归想成堆栈 - public static void printToMax3(int n) { - if (n <= 0) return; - char[] number = new char[n]; - for (int i = 0; i < 10; i++) { - number[0] = (char) (i + '0'); - printToMax3Recur(number, n, 0); - } - } - - private static void printToMax3Recur(char[] number, int n, int index) { - if (index == n - 1) { - printNumber(number); - return; - } - for (int j = 0; j < 10; j++) { - number[index + 1] = (char) (j + '0'); - printToMax3Recur(number, n, index + 1); - } - } -} diff --git a/SwordOffer/src/books/T18.java b/SwordOffer/src/books/T18.java deleted file mode 100644 index fce1e6c7..00000000 --- a/SwordOffer/src/books/T18.java +++ /dev/null @@ -1,99 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 删除链表的节点 & 删除链表中重复的节点 - * @author: mf - * @create: 2019/08/29 14:49 - */ - -/* -在O(1)时间内删除链表节点 -给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点 -链表节点与函数的定义如下 - */ - -public class T18 { - public static void main(String[] args) { - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - ListNode listNode4 = new ListNode(4); - - listNode1.next = listNode2; - listNode2.next = listNode3; - listNode3.next = listNode4; - System.out.println("source ListNode..."); - printListNode(listNode1); - deleteNode(listNode1, listNode3); - System.out.println("deleted ListNode..."); - printListNode(listNode1); - -// ListNode listNode1 = new ListNode(1); -// ListNode listNode2 = new ListNode(2); -// ListNode listNode3 = new ListNode(2); -// ListNode listNode4 = new ListNode(3); -// -// listNode1.next = listNode2; -// listNode2.next = listNode3; -// listNode3.next = listNode4; -// System.out.println("source"); -// printListNode(listNode1); -// deleteDuplication(listNode1); -// System.out.println("deleted"); -// printListNode(listNode1); - } - - private static void deleteNode(ListNode headListNode, ListNode pListNode) { - // 要删除的节点不是尾节点 - if (pListNode.next != null) { - ListNode node = pListNode.next; - pListNode.value = node.value; - pListNode.next = node.next; - } else if (headListNode == pListNode) { // 只有一个头节点 - pListNode = null; - headListNode = null; - } else { // 链表中有多个节点,删除的是尾节点 - // 只能遍历了 - ListNode node = headListNode; - while (node.next != pListNode) { - node = node.next; - } - node.next = null; - pListNode = null; - } - } - - private static void deleteDuplication(ListNode headListNode) { - if (headListNode == null) return; - ListNode preNode = null; - ListNode pNode = headListNode; - while (pNode != null) { - ListNode pNext = pNode.next; - boolean needDelete = false; - if (pNext != null && pNext.value == pNode.value) needDelete = true; - if (!needDelete) { - preNode = pNode; - pNode = pNext; - } else { - int value = pNode.value; - ListNode pDelNode = pNode; - while (pDelNode != null && pDelNode.value == value) { - pNext = pDelNode.next; - pDelNode = pNext; - } - if (preNode == null) headListNode = pNext; - else pNode.next = pNext; -// else preNode.next = pNext; -// pNode = pNext; - } - } - } - public static void printListNode(ListNode headListnode) { - while (headListnode != null) { - System.out.println(headListnode.value); - headListnode = headListnode.next; - } - } - -} diff --git a/SwordOffer/src/books/T19.java b/SwordOffer/src/books/T19.java deleted file mode 100644 index b04c0c1d..00000000 --- a/SwordOffer/src/books/T19.java +++ /dev/null @@ -1,69 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 正则表达式 - * @author: mf - * @create: 2019/08/31 13:42 - */ - -/* -请实现一个函数用来匹配包括'.'和'*'的正则表达式。 -模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 -在本题中,匹配是指字符串的所有字符匹配整个模式。例如 -字符串'aaa'与模式'a.a'和'ab*ac*a'匹配,但是与'aa.a'和'ab*a'均不匹配 - */ - -/* -思路: -当模式中的的第二个字符不是'*'时: - 1、 如果字符串第一个字符和模式的一个字符相匹配,那么字符串和模式都往后移一个字符,然后匹配剩余的。 - 2、 否则,直接返回false - -当模式中的第二个字符是'*'时: - 1、 如果字符串第一个字符和模式第一个字符不匹配,则模式后移2个字符,继续匹配。 - 2、 否则,有3种匹配方式: - 1、 字符串不变,模式后移2 - 2、 字符串后移1,模式后移2 - 3、 字符串后移1,模式不变 - */ -public class T19 { - public static void main(String[] args) { - char[] s = {'a', 'a', 'a'}; - char[] p = {'a', 'b', '*', 'a', 'c', '*', 'a'}; - boolean res = match(s, p); - System.out.println(res); - } - - private static boolean match(char[] s, char[] p) { - if (s == null || p == null) return false; - int sIndex = 0; - int pIndex = 0; - return matchCore(s, sIndex, p, pIndex); - } - - private static boolean matchCore(char[] s, int sIndex, char[] p, int pIndex) { - // s 到尾,p 到尾,匹配成功 - if (sIndex == s.length && pIndex == p.length) return true; - // p 先到尾, 匹配失败 - if (sIndex != s.length && pIndex == p.length) return false; - // p 第二字符是 *,且字符串第一个跟模式第一个匹配;如果不匹配,模式后移动两位 - if (pIndex + 1 < p.length && p[pIndex + 1] == '*') { - if ((sIndex != s.length && p[pIndex] == s[sIndex]) || (p[pIndex] == '.' && sIndex != s.length)) { - // 1. s不变,p后移2字符,相当于x*被忽略 - // 2. s后移1字符,p后移2字符 - // 3. s后移1字符,p不变 - return matchCore(s, sIndex, p, pIndex + 2) || matchCore(s, sIndex + 1, p, pIndex + 2) - || matchCore(s, sIndex + 1, p, pIndex); - } else { - return matchCore(s, sIndex, p, pIndex + 2); - } - } - - // p 第二个不是 *,且字符串第一个跟模式第一个匹配,则后都移一位,否则直接返回false - if ((sIndex != s.length && p[pIndex] == s[sIndex]) || (p[pIndex] == '.' && sIndex != s.length)) { - return matchCore(s, sIndex + 1, p, pIndex + 1); - } - return false; - } -} diff --git a/SwordOffer/src/books/T2.java b/SwordOffer/src/books/T2.java deleted file mode 100644 index 72d52e16..00000000 --- a/SwordOffer/src/books/T2.java +++ /dev/null @@ -1,87 +0,0 @@ -package books; /** - * @program JavaBooks - * @description: 单例模式 - * @author: mf - * @create: 2019/08/15 15:53 - */ - -/** - * 饿汉 - */ -public class T2 { - private static T2 instance = new T2(); - private T2(){} - - public static T2 getInstance() { - return instance; - } -} - -/** - * 饿汉变种 - */ -class Singleton1 { - private static Singleton1 instance = null; - static { - instance = new Singleton1(); - } - private Singleton1() {} - - public static Singleton1 getInstance() { - return instance; - } -} - -/** - * 懒汉 -- 线程不安全... - */ -class Singleton2 { - private static Singleton2 instance = null; - private Singleton2(){} - - public static Singleton2 getInstance() { - if (instance == null){ - instance = new Singleton2(); - } - return instance; - - } -} - -/** - * 懒汉 -- 线程安全, 但消耗资源较为严重 - */ -class Singleton3 { - private static Singleton3 instance = null; - - private Singleton3() { - } - - public static synchronized Singleton3 getInstance() { - if (instance == null) { - instance = new Singleton3(); - } - return instance; - } -} - -/** - * 线程安全,双重校验 - */ -class Singleton4 { - private static volatile Singleton4 instance = null; - - private Singleton4() { - } - - public static Singleton4 getInstance() { - if (instance == null) { - synchronized (Singleton4.class) { - if (instance == null) { - instance = new Singleton4(); - } - } - } - return instance; - } -} \ No newline at end of file diff --git a/SwordOffer/src/books/T20.java b/SwordOffer/src/books/T20.java deleted file mode 100644 index c6fd143a..00000000 --- a/SwordOffer/src/books/T20.java +++ /dev/null @@ -1,74 +0,0 @@ -package books; - -import javax.naming.RefAddr; - -/** - * @program JavaBooks - * @description: 表示数值的字符串 - * @author: mf - * @create: 2019/09/01 10:16 - */ - -/* -请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如 -字符串"+100"、"5e2"、"-123"、"3。1416"及"-1E-16"都表示数值。 -但"12e"、"1a3.14"、"1.2.3"、"+-5"及"12e+5.4"都不是 - */ - -/* -数字的格式可以用A[.[B]][e|EC]表示,其中A和C都是 -整数(可以有正负号,也可以没有),而B是一个无符号整数 - */ -public class T20 { - public static void main(String[] args) { - char[] str = {'1', '2', '3', '.', '4', '5', 'e', '+', '6'}; - boolean res = isNumeric(str); - System.out.println(res); - } - - private static boolean isNumeric(char[] str) { - int index = 0; - if (str == null || str.length == 0) return false; - if (str.length == 1 && (str[0] == '+' || str[0] == '-')) return false; // 说明只有一个字符,要不+要不- - if (str[0] == '+' || str[0] == '-') index++; // 跳过+ 或者- - index = judgeDigits(str, index); // 跳过整数的数字部分 - if (index == str.length) return true; // 正好满足 - if (str[index] == '.') { - // 跳过整数, 就是小数点了 - //跳过小数点 - index++; - if (index == str.length) return false; // 不满足 - index = judgeDigits(str, index); // 跳过小数点后的整数部分 - if (index == str.length) return true; // 正好满足就返回 - if (str[index] == 'e' || str[index] == 'E') { - index++; // 吧e和E跳过去 - return judgeE(str, index); - } - return false; - } else if(str[index] == 'e' || str[index] == 'E') { - index++; // 吧e和E跳过去 - return judgeE(str, index); - } - - return false; - } - - private static boolean judgeE(char[] str, int index) { - if (index >= str.length) return false; - if (str[index] == '+' || str[index] == '-') index++; // 跳过+ 或者- - if (index >= str.length) return false;//如果刚跳过e就到了字符串末尾 是12e就是不规范的 - index = judgeDigits(str, index); // 跳过数字部分部分 - if (index == str.length) return true; - return false; - } - - private static int judgeDigits(char[] str, int index) { - while (index < str.length) { - // 判断是不是0-9之间,不是的话就break返回index下标 - int number = str[index] - '0'; - if (number <= 9 && number >= 0) index++; - else break; - } - return index; - } -} diff --git a/SwordOffer/src/books/T21.java b/SwordOffer/src/books/T21.java deleted file mode 100644 index 9f367c57..00000000 --- a/SwordOffer/src/books/T21.java +++ /dev/null @@ -1,48 +0,0 @@ -package books; - -import java.util.Arrays; - -/** - * @program JavaBooks - * @description: 调整数组顺序使奇数位于偶数前面 - * @author: mf - * @create: 2019/09/02 09:47 - */ -/* -输入一个整数数组,实现一个函数来调整该数组中数字的顺序, -使得所有奇数位于数组的前半部分 -所有偶数位于数组的后半部分 - */ -public class T21 { - public static void main(String[] args) { - int[] arr = {2, 3, 6, 4, 7, 5}; - reorderOddEven(arr); - System.out.println(Arrays.toString(arr)); - } - - private static void reorderOddEven(int[] arr) { - if (arr == null || arr.length == 0) return; - int p1 = 0; - int p2 = arr.length - 1; - while (p1 < p2) { - // 向后移动p1, 直到指向偶数 根据题目的话,这里可扩展 - while (p1 < p2 && (arr[p1] & 0x1) != 0) { - p1++; - } - // 向前移动p2,直到指向奇数 同理 - while (p1 < p2 && (arr[p2] & 0x1) != 1) { - p2--; - } - - if(p1 < p2) { - swap(arr, p1, p2); - } - } - } - - private static void swap(int[] arr, int p1, int p2) { - int temp = arr[p1]; - arr[p1] = arr[p2]; - arr[p2] = temp; - } -} diff --git a/SwordOffer/src/books/T22.java b/SwordOffer/src/books/T22.java deleted file mode 100644 index 5fcd4933..00000000 --- a/SwordOffer/src/books/T22.java +++ /dev/null @@ -1,93 +0,0 @@ -package books; - -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 链表中倒数第K个节点 - * @author: mf - * @create: 2019/09/03 09:32 - */ - -/* -输入一个链表,输出该链表中倒数第K个节点。为了符合大多数 -人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。 -例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3 -、4、5、6。这个链表的倒数第3个节点是值为4的节点。链表的定义如下: - */ - - -/* -思路 -准备两个指针p1 p2 -当p1++ 到k的时候,p2开始++ -当当尾节点的时候,p2正好是倒数k个节点 n-k+1 -追赶思路。。。 - */ -public class T22 { - public static void main(String[] args) { - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - ListNode listNode4 = new ListNode(4); - ListNode listNode5 = new ListNode(5); - - listNode1.next = listNode2; - listNode2.next = listNode3; - listNode3.next = listNode4; - listNode4.next = listNode5; - - ListNode kNode = findKthToTail(listNode1, 6); - System.out.println(kNode.value); - } - - /** - * 快慢指针问题 - * @param headListNode - * @param k - * @return - */ - public static ListNode findKthToTail(ListNode headListNode, int k) { - if (headListNode == null || k == 0 ) return null; - // - ListNode pNode = headListNode; - ListNode kNode = headListNode; - int p1 = 0; - while (pNode != null) { - if (p1 > k - 1) { // 当p1走完k-1步,那么kNode开始走 - kNode = kNode.next; - } - pNode = pNode.next; - p1++; - } - if (p1 >= k) { - return kNode; - } - return null; - } - - /** - * 栈 - * @param headListNode - * @param k - * @return - */ - public static ListNode findKthToTail2(ListNode headListNode, int k) { - if(headListNode == null || k <= 0) return null; - Stack stack = new Stack<>(); - ListNode root = headListNode; - while(root != null) { - stack.push(root); - root = root.next; - } - int temp = 0; - while(!stack.isEmpty()) { - ListNode listNode = stack.pop(); - temp++; - if (temp == k) { - return listNode; - } - } - return null; - } -} diff --git a/SwordOffer/src/books/T23.java b/SwordOffer/src/books/T23.java deleted file mode 100644 index 2bb09608..00000000 --- a/SwordOffer/src/books/T23.java +++ /dev/null @@ -1,74 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 链表中环的入口节点 - * @author: mf - * @create: 2019/09/04 14:28 - */ - -/* - - */ -public class T23 { - public static void main(String[] args) { - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - ListNode listNode4 = new ListNode(4); - ListNode listNode5 = new ListNode(5); - - listNode1.next = listNode2; - listNode2.next = listNode3; - listNode3.next = listNode4; - listNode4.next = listNode5; - listNode5.next = listNode3; - ListNode enterNode = findEnterNode(listNode1); - System.out.println(enterNode.value); - } - - private static ListNode findEnterNode(ListNode headNode) { - if (headNode == null) return null; - ListNode meetingNode = findMeetingNode(headNode); - if (meetingNode == null) return null; // 无环 - - // 找环中有几个节点 - ListNode tempNode = meetingNode.next; - int ringNum = 1; - while (tempNode != meetingNode) { - tempNode = tempNode.next; - ringNum++; - } - - // 设定两个指针,比如p1 p2 - // 一开始都在头节点,当p1 跑到ringNum的时候,p2开始移动 - // 当p1 == p2 的时候, 就是环入口节点 - ListNode enterNode = headNode; - headNode = headNode.next; - int p1 = 1; - while (enterNode != headNode) { - headNode = headNode.next; - p1++; - if (p1 > ringNum) { - enterNode = enterNode.next; - } - } - return enterNode; - } - - private static ListNode findMeetingNode(ListNode headNode) { - ListNode slowNode = headNode.next; - if (slowNode == null) return null; - ListNode fastNode = slowNode.next; - while (fastNode != null && slowNode != null) { - if (fastNode == slowNode) return fastNode; - slowNode = slowNode.next; - fastNode = fastNode.next; - if (fastNode != null) fastNode = fastNode.next; // 提高代码的鲁棒性而已 -// fastNode = fastNode.next; - } - return null; - } - - -} diff --git a/SwordOffer/src/books/T24.java b/SwordOffer/src/books/T24.java deleted file mode 100644 index d02b9ecc..00000000 --- a/SwordOffer/src/books/T24.java +++ /dev/null @@ -1,70 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 反转链表 - * @author: mf - * @create: 2019/09/05 09:55 - */ - -/* -定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表 -头节点。 - */ - -/* -思路: -设定三个指针,pre p next, 交换即可,但交换之前检查next是否为空,以防锻炼 - */ -public class T24 { - public static void main(String[] args) { - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - ListNode listNode4 = new ListNode(4); - ListNode listNode5 = new ListNode(5); - - listNode1.next = listNode2; - listNode2.next = listNode3; - listNode3.next = listNode4; - listNode4.next = listNode5; - - ListNode headNode = reverseListNode(listNode1); - System.out.println(headNode.value); - } - - /** - * 循环 - * @param headNode - * @return - */ - private static ListNode reverseListNode(ListNode headNode) { - if (headNode == null) return null; - ListNode pre = null; // 当前节点的前一个节点 - ListNode cur = headNode; // 当前节点 - while (cur != null) { - ListNode next = cur.next; // 存一下当前节点的下一个节点 - cur.next = pre; // 将当前节点的下一个节点直接指向当前节点的pre - pre = cur; // 前一个节点指向当前节点 - cur = next; // 当前节点指向下一个节点 - } - return pre; - } - - /** - * 尾递归 - * @param headNode - * @return - */ - private static ListNode reverseListNode2(ListNode headNode) { - if (headNode == null) return null; - return reverse(null, headNode); - } - - private static ListNode reverse(ListNode pre, ListNode cur) { - if (cur == null) return pre; - ListNode next = cur.next; - cur.next = pre; - return reverse(cur, next); - } -} diff --git a/SwordOffer/src/books/T25.java b/SwordOffer/src/books/T25.java deleted file mode 100644 index c546e891..00000000 --- a/SwordOffer/src/books/T25.java +++ /dev/null @@ -1,64 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 合并两个排序的链表 - * @author: mf - * @create: 2019/09/06 09:19 - */ - -/* -输入两个递增排序的链表,合并这两个链表并使新链表中的节点 -仍然使递增排序的。例如,。。。 -1 3 5 -2 4 6 -1 2 3 4 5 6 - */ - -/* -思路还是类似于准备两个指针一样,但是这次不同 -比较两个值,谁小,谁让出来, -比如, p1 < p2 -那么,node = p1 -那么node.next 继续和p2比较, 如果还小, 继续上面的过程,所以递归(因为p1 p2已经排好序) -如果p1 >= p2,那就将node = p2, node.next 和p1剩下的比较, 继续上面的过程 - - */ -public class T25 { - public static void main(String[] args) { - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - ListNode listNode4 = new ListNode(4); - ListNode listNode5 = new ListNode(5); - ListNode listNode6 = new ListNode(6); - - - listNode1.next = listNode3; - listNode3.next = listNode5; - - listNode2.next = listNode4; - listNode4.next = listNode6; - - ListNode node = merge(listNode1, listNode2); - while (node != null) { - System.out.println(node.value); - node = node.next; - } - } - - // 递归 - private static ListNode merge(ListNode headNode1, ListNode headNode2) { - if (headNode1 == null) return headNode2; - if (headNode2 == null) return headNode1; - ListNode node = null; - if (headNode1.value < headNode2.value) { - node = headNode1; - node.next = merge(node.next, headNode2); - } else { - node = headNode2; - node.next = merge(node.next, headNode1); - } - return node; - } -} diff --git a/SwordOffer/src/books/T26.java b/SwordOffer/src/books/T26.java deleted file mode 100644 index 5f80c8d9..00000000 --- a/SwordOffer/src/books/T26.java +++ /dev/null @@ -1,69 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 树的子结构 - * @author: mf - * @create: 2019/09/06 10:07 - */ - -/* -输入两颗二叉树A和B,判断B是不是A的子结构。 - */ - -/* -思路 -两个递归, 第一个大递归的功能是遍历A的node和B树root是否相等,终止条件就是是否为空,如果都不相等,最后肯定是false -当相等的时候,那第二个递归的功能就是同时遍历A树和B树的对应的左右子节点是否相等,终止条件就是 - */ -public class T26 { - public static void main(String[] args) { - int[] preA = {8, 8, 9, 2, 4, 7, 7}; - int[] inA = {9, 8, 4, 2, 7, 8, 7}; - - int[] preB = {8, 9 , 2}; - int[] inB = {9, 8, 2}; - - // 根据笔试题T7, 前序列和中序,重建二叉树 - TreeNode nodeA = TreeNode.setBinaryTree(preA, inA); - TreeNode nodeB = TreeNode.setBinaryTree(preB, inB); - -// System.out.println("前序A:"); -// TreeNode.preOrderRe(nodeA); -// System.out.println("前序B:"); -// TreeNode.preOrderRe(nodeB); - - boolean isSubTree = hasSubTree(nodeA, nodeB); - System.out.println(isSubTree); - } - - private static boolean hasSubTree(TreeNode nodeA, TreeNode nodeB) { - - boolean result = false; - // 注意条件 - if (nodeA != null && nodeB != null) { - if (nodeA.val == nodeB.val) { - // root's val equal , so 开始遍历子节点 - result = goOnFind(nodeA, nodeB); - } - // 递归找左 - if (!result) { - result = hasSubTree(nodeA.left, nodeB); - } - // 递归找右 - if (!result) { - result = hasSubTree(nodeA.right, nodeB); - } - } - - return result; - } - - private static boolean goOnFind(TreeNode nodeA, TreeNode nodeB) { - if (nodeB == null) return true; // 说明b提前遍历完成 - if (nodeA == null) return false; // 说明B还有节点,A没有子节点等 - if (nodeA.val != nodeB.val) return false; // 说明值不相等 - // 递归子节点 - return goOnFind(nodeA.left, nodeB.left) && goOnFind(nodeA.right, nodeB.right); - } -} diff --git a/SwordOffer/src/books/T27.java b/SwordOffer/src/books/T27.java deleted file mode 100644 index 7cce682c..00000000 --- a/SwordOffer/src/books/T27.java +++ /dev/null @@ -1,46 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二叉树的镜像 - * @author: mf - * @create: 2019/09/08 10:20 - */ - -/* -请完成一个函数,输入一颗二叉树,该函数输出它的镜像 - - */ - -/* -root的子节点左右互选,发现有规律,那么依次类堆 - */ -public class T27 { - public static void main(String[] args) { - int[] pre = {8, 6, 5, 7, 10, 9, 11}; - int[] in = {5, 6, 7, 8, 9, 10, 11}; - TreeNode node = TreeNode.setBinaryTree(pre, in); - - System.out.println("镜像前:"); - TreeNode.preOrderRe(node); - MirrorRec(node); - System.out.println("镜像后:"); - TreeNode.preOrderRe(node); - } - - private static void MirrorRec(TreeNode node) { - if (node == null) return; - if (node.left == null && node.right == null) return; - swap(node, node.left, node.right); - - // 递归 - if (node.left != null) MirrorRec(node.left); - if (node.right != null) MirrorRec(node.right); - } - - private static void swap(TreeNode node, TreeNode left, TreeNode right) { - TreeNode temp = left; - node.left = right; - node.right = temp; - } -} diff --git a/SwordOffer/src/books/T28.java b/SwordOffer/src/books/T28.java deleted file mode 100644 index 1e9d7b71..00000000 --- a/SwordOffer/src/books/T28.java +++ /dev/null @@ -1,35 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 对称的二叉树 - * @author: mf - * @create: 2019/09/09 10:11 - */ - -/* -请实现一个函数,用来判断一颗二叉树是不是对称的。如果一颗二叉树 -和它的镜像一样,那么它是对称的。具体举例请看书 - */ -/* -思路 -前序遍历和对称的前序遍历是一样的,这就是规律。 -但注意的是考虑空值,以防二叉树不完整 - */ -public class T28 { - public static void main(String[] args) { - int[] pre = {8, 6, 5, 7, 6, 7, 5}; - int[] in = {5, 6, 7, 8, 7, 6, 5}; - TreeNode node = TreeNode.setBinaryTree(pre, in); - boolean res = isSymmetrical(node, node); - System.out.println(res); - } - - private static boolean isSymmetrical(TreeNode node, TreeNode node1) { - if (node == null && node1 == null) return true; // 遍历到底了 - if (node == null || node1 == null) return false; // 以防二叉树不完整 - if (node.val != node1.val) return false; // 互为对称的值是相等的。 - // node.left right 前序, node1 right left 对称前序遍历 - return isSymmetrical(node.left, node1.right) && isSymmetrical(node.right, node1.left); - } -} diff --git a/SwordOffer/src/books/T29.java b/SwordOffer/src/books/T29.java deleted file mode 100644 index bb47190f..00000000 --- a/SwordOffer/src/books/T29.java +++ /dev/null @@ -1,76 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 顺时针打印矩阵 - * @author: mf - * @create: 2019/09/10 10:31 - */ - -/* -输入一个矩阵,按照从外向里以顺时针依次打印出每一个数字。 -例如 - 1 2 3 4 - 5 6 7 8 - 9 10 11 12 - 13 14 15 16 - */ - -/* -思路: -考虑圈的截止条件,从左上作为圈的开始和结束 -然后按圈打印,注意按圈打印的四个边界条件。 - - */ -public class T29 { - public static void main(String[] args) { - int[][] arr = { - {1, 2, 3, 4}, - {5, 6, 7, 8}, - {9, 10, 11, 12}, - {13, 14, 15, 16} - }; - printMatrixClock(arr, arr.length, arr[0].length); - } - - private static void printMatrixClock(int[][] arr, int rows, int cols) { - if (arr == null || rows <= 0 || cols <= 0) return; - int start = 0; - while (rows > start * 2 && cols > start * 2) {// 判断打圈的条件 - printMatrixCircle(arr, rows, cols, start); // 打印圈 - start++; - } - } - - private static void printMatrixCircle(int[][] arr, int rows, int cols, int start) { - int endX = cols - 1 - start; - int endY = rows - 1 - start; - // 从左到右打印一行 - for (int i = start; i <= endX; i++) { - System.out.print(arr[start][i]); - System.out.print('\t'); - } - // 从上到下打印一列 - if (start < endY) { - for (int i = start + 1; i <= endY; i++) { - System.out.print(arr[i][endX]); - System.out.print('\t'); - } - } - // 从右往左打印一行 - if (start < endX && start < endY) { - for (int i = endX - 1; i >= start; i--) { - System.out.print(arr[endY][i]); - System.out.print('\t'); - } - } - - // 从下往上打印一列 - if (start < endX && start < endY) { - for (int i = endY - 1; i >= start + 1; i--) { - System.out.print(arr[i][start]); - System.out.print('\t'); - } - } - } -} diff --git a/SwordOffer/src/books/T3.java b/SwordOffer/src/books/T3.java deleted file mode 100644 index cdb51d1f..00000000 --- a/SwordOffer/src/books/T3.java +++ /dev/null @@ -1,90 +0,0 @@ -package books; - -import java.util.ArrayList; -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 数组中重复的数字 - * @author: mf - * @create: 2019/08/16 19:22 - */ - - -/* -在一个长度为n的数组中里的所有数字都在0~n-1的范围中。数组中某些数字是重复的, -但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复 -的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出 -重复的数字2或者3 - */ -public class T3 { - public static void main(String[] args) { - int[] arr = {2, 3, 1, 0, 2, 5, 3}; - - // 第一种 可修改数组 - ArrayList helpList = duplication(arr); - System.out.println(helpList); - - int[] arr1 = {2, 3, 1, 0, 2, 5, 3}; - // 第二种,哈希表 -- 不可修改数组 - ArrayList helpList1 = duplication2(arr1); - System.out.println(helpList1); - - // 刚才的第一种,可以添加辅助数组来解决不可修改数组的方案 - // 或者用二分查找...以时间换空间... - - } - - /** - * 可修改数组 - * @param arr - * @return - */ - public static ArrayList duplication(int[] arr) { - ArrayList helpList = new ArrayList<>(); - // 提高鲁棒性 - for (int i : arr) { - if (i < 0 || i > arr.length - 1) { - System.out.println("arr is not true..."); - break; - } - } - // 核心步骤 - for (int i = 0; i < arr.length; i++) { - while (arr[i] != i) { - if (arr[i] == arr[arr[i]]) { - helpList.add(arr[i]); - arr[i] = i; - break; - } - swap(arr, arr[i], arr[arr[i]]); - } - } - return helpList; - - } - - public static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } - - /** - * 哈希 - * @param arr - * @return - */ - public static ArrayList duplication2(int[] arr) { - HashMap hashMap = new HashMap<>(); - ArrayList helpList = new ArrayList<>(); - for (int i : arr) { - if (hashMap.containsKey(i)) { - helpList.add(i); - } - hashMap.put(i,i); - } - return helpList; - } - -} diff --git a/SwordOffer/src/books/T30.java b/SwordOffer/src/books/T30.java deleted file mode 100644 index 2c3cd03e..00000000 --- a/SwordOffer/src/books/T30.java +++ /dev/null @@ -1,57 +0,0 @@ -package books; - -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 包含min函数的栈 - * @author: mf - * @create: 2019/09/11 10:12 - */ - -/* -定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素 -的min函数。在干栈中,调用min、push及pop的时间复杂度都是o(1) - */ - -/* -思路 -一般来个辅助栈 -好多题, 都需要辅助空间的 -辅助栈的话,就很简单了。思路也很明了了。 - */ -public class T30 { - public static void main(String[] args) { - Stack stack = new Stack<>(); - Stack helpStack = new Stack<>(); - - dataPush(stack, helpStack, 3); - dataPush(stack, helpStack, 2); - dataPush(stack, helpStack, 1); - dataPush(stack, helpStack, 5); - dataPush(stack, helpStack, 0); -// dataPop(stack, helpStack); - Integer value = dataMin(stack, helpStack); - System.out.println(value); - } - - private static Integer dataMin(Stack stack, Stack helpStack) { - if (stack.isEmpty() || helpStack.isEmpty()) return null; - return helpStack.peek(); - } - - private static void dataPop(Stack stack, Stack helpStack) { - if (stack.empty() || helpStack.empty()) return; - stack.pop(); - helpStack.pop(); - } - - private static void dataPush(Stack stack, Stack helpStack, int value) { - stack.push(value); - if (helpStack.empty() || value < helpStack.peek()) { - helpStack.push(value); - } else { - helpStack.push(helpStack.peek()); - } - } -} diff --git a/SwordOffer/src/books/T31.java b/SwordOffer/src/books/T31.java deleted file mode 100644 index 2d055449..00000000 --- a/SwordOffer/src/books/T31.java +++ /dev/null @@ -1,48 +0,0 @@ -package books; - -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 栈的压入、弹出序列 - * @author: mf - * @create: 2019/09/12 10:16 - */ - -/* -输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈 -的弹出顺序。假设压入栈的所有数字均不相等。例如,序列{1, 2, 3,4, 5} -是某栈的压栈序列,序列{4,5,3,2,1}是该压栈序列对应的一个弹出序列,但 -{4,3,5,1,2}就不肯能是该压栈序列的弹出序列。 - */ - -/* -思路: -用一个Pop的指针即可 -每次压栈,取出栈顶去和当前pop的值做比较,若相等,pop++ -并且stack弹出栈顶 - */ -public class T31 { - public static void main(String[] args) { - int[] arrPush = {1, 2, 3, 4, 5}; - int[] arrPop = {4, 5, 3, 2, 1}; - int[] arrPop1 = {4, 3, 5, 1, 2}; - boolean res = isPopOrder(arrPush, arrPop1); - System.out.println(res); - } - - private static boolean isPopOrder(int[] arrPush, int[] arrPop) { - if (arrPush == null || arrPop == null) return false; - int pPop = 0; - Stack stack = new Stack<>(); - for (int i = 0; i < arrPush.length; i++) { - stack.push(arrPush[i]); - while (!stack.empty() && stack.peek() == arrPop[pPop]) { - stack.pop(); - pPop++; - } - } - - return stack.empty(); - } -} diff --git a/SwordOffer/src/books/T32.java b/SwordOffer/src/books/T32.java deleted file mode 100644 index 4394b888..00000000 --- a/SwordOffer/src/books/T32.java +++ /dev/null @@ -1,78 +0,0 @@ -package books; - -import java.util.concurrent.LinkedBlockingQueue; - -/** - * @program JavaBooks - * @description: 从上到下打印二叉树 - * @author: mf - * @create: 2019/09/13 12:38 - */ - -/* -不分行从下到上打印二叉树 -从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印 -例如,见书 - */ - - -/* -思路 -这个题需要辅助队列容器 -也就是说, 从上往下打印某个节点时,该节点的两个子节点不为空,就加进容器中, -直到打印到容器为空为止。 - */ -public class T32 { - public static void main(String[] args) { - int[] pre = {8, 6, 5, 7, 10, 9, 11}; - int[] in = {5, 6, 7, 8, 9, 10, 11}; - TreeNode node = TreeNode.setBinaryTree(pre, in); -// printNode(node); - printNode2(node); - } - - private static void printNode(TreeNode node) { - if (node == null) return; - // new一个队列 - LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); - - queue.offer(node); - while (!queue.isEmpty()) { - TreeNode tempNode = queue.poll(); - System.out.print(tempNode.val + "\t"); - if (tempNode.left != null) { - queue.offer(tempNode.left); - } - if (tempNode.right != null) { - queue.offer(tempNode.right); - } - } - } - // 分行打印 - private static void printNode2(TreeNode node) { - if (node == null) return; - LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); - queue.offer(node); - int cLevel = 1; - int nextLevel = 0; - while (!queue.isEmpty()) { - TreeNode tempNode = queue.poll(); - System.out.print(tempNode.val + "\t"); - cLevel--; - if (tempNode.left != null) { - queue.offer(tempNode.left); - nextLevel++; - } - if (tempNode.right != null) { - queue.offer(tempNode.right); - nextLevel++; - } - if (cLevel == 0) { - System.out.print("\n"); - cLevel = nextLevel; - nextLevel = 0; - } - - } - } -} diff --git a/SwordOffer/src/books/T33.java b/SwordOffer/src/books/T33.java deleted file mode 100644 index bdb357bc..00000000 --- a/SwordOffer/src/books/T33.java +++ /dev/null @@ -1,55 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二叉搜索树的后序遍历序列 - * @author: mf - * @create: 2019/09/14 21:15 - */ - -/* -输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果 -如果是则返回true,否则返回false。假设输入的数组的任意两个数字 -都互不相同。例如,输入数组{5, 7, 6, 9, 11, 10, 8}, 则返回true -因为这个整数序列是书上图4.9二叉搜索树的后序遍历结果。如果输入的数组是 -{7,4,6,5} - */ -public class T33 { - public static void main(String[] args) { - int[] sequence = {5, 7, 6, 9, 11, 10, 8}; - boolean res = VerifySquenceOfBST(sequence); - System.out.println(res); - } - - private static boolean VerifySquenceOfBST(int[] sequence) { - if (sequence == null || sequence.length == 0) return false; - return isBST(sequence, 0, sequence.length - 1); - } - - private static boolean isBST(int[] sequence, int start, int end) { - if (start >= end) { - return true; - } - int inx = sequence[end]; - int m = start; - // 找到分界点 - for (int i = end - 1; i >= start; i--) { - if (sequence[i] < inx) { - m = i; - break; - } - if (i == start) { - m = -1; - } - } - // 分界点前的数据都小于根节点 - for (int i = start; i <= m; i++) { - if (sequence[i] > inx) { - return false; - } - } - // 递归判断根节点的左右子树 - return isBST(sequence, start, m) && isBST(sequence, m + 1, end - 1); - - } -} diff --git a/SwordOffer/src/books/T34.java b/SwordOffer/src/books/T34.java deleted file mode 100644 index 92bb1c04..00000000 --- a/SwordOffer/src/books/T34.java +++ /dev/null @@ -1,51 +0,0 @@ -package books; - -import java.util.ArrayList; - -/** - * @program JavaBooks - * @description: 二叉树中和为某一值的路径 - * @author: mf - * @create: 2019/09/15 13:56 - */ - -/* -输入一颗二叉树和一个整数,打印出二叉树中节点值的和 -为输入整数的所有路径。从树的根节点开始往下一直到叶节点 -所经过的节点形成一条路径。 - */ - -/* -思路: -准备两个容器 -一个存放走过来的路径 -一个存放和为target的路径 -递归的过程,前序遍历 -用第一个容器去记录节点值, -每当路径走完计算和是否为target, 不管等不等,都需要减掉最后节点 - */ -public class T34 { - private static ArrayList> listall = new ArrayList<>(); - private static ArrayList lists = new ArrayList<>(); - - public static void main(String[] args) { - int[] pre = {10, 5, 4, 7, 12}; - int[] in = {4, 5, 7, 10, 12}; - TreeNode node = TreeNode.setBinaryTree(pre, in); - ArrayList> resList = findPath(node, 22); - System.out.println(resList); - } - - private static ArrayList> findPath(TreeNode node, int target) { - if (node == null) return listall; - lists.add(node.val); - target -= node.val; - if (target == 0 && node.left == null && node.right == null) { - listall.add(new ArrayList<>(lists)); - } - findPath(node.left, target); - findPath(node.right, target); - lists.remove(lists.size() - 1); - return listall; - } -} diff --git a/SwordOffer/src/books/T35.java b/SwordOffer/src/books/T35.java deleted file mode 100644 index 810f5f04..00000000 --- a/SwordOffer/src/books/T35.java +++ /dev/null @@ -1,67 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 复杂链表的复制 - * @author: mf - * @create: 2019/09/16 12:06 - */ - -/* -见书 - */ - -/* -思路: -1. 复制每个节点,如复制节点A得到A1,将节点A1插到节点A后面 -2. 重新遍历链表,复制老结点的随机指针给新节点,如 A1.random = A.random.next; -3. 拆分链表,将链表拆分为原链表和复制后的链表 - */ -public class T35 { - public static void main(String[] args) { - ListNode node1 = new ListNode(1); - ListNode node2 = new ListNode(2); - ListNode node3 = new ListNode(3); - ListNode node4 = new ListNode(4); - ListNode node5 = new ListNode(5); - - node1.next = node2; - node2.next = node3; - node3.next = node4; - node4.next = node5; - - node1.random = node3; - node2.random = node5; - node4.random = node2; - - ListNode resNode = Clone(node1); - } - - private static ListNode Clone(ListNode pHead) { - if (pHead == null) return null; - ListNode node = pHead; - // 1. 复制每个节点,如复制节点A得到A1,将节点A1插到节点A后面 - while (node != null) { - ListNode copyNode = new ListNode(node.value); - copyNode.next = node.next; - node.next = copyNode; - node = copyNode.next; - } - node = pHead; - // 2. 重新遍历链表,复制老结点的随机指针给新节点,如 A1.random = A.random.next; - while (node != null) { - node.next.random = node.random == null ? null : node.random.next; - node = node.next.next; - } - // 3. 拆分链表,将链表拆分为原链表和复制后的链表 - node = pHead; - ListNode pCloneHead = pHead.next; - while (node != null) { - ListNode copyNode = node.next; - node.next = copyNode.next; - copyNode.next = copyNode.next == null ? null : copyNode.next.next; - node = node.next; - } - return pCloneHead; - } -} diff --git a/SwordOffer/src/books/T36.java b/SwordOffer/src/books/T36.java deleted file mode 100644 index 8b456eff..00000000 --- a/SwordOffer/src/books/T36.java +++ /dev/null @@ -1,43 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二叉搜索树与双向链表 - * @author: mf - * @create: 2019/09/17 09:54 - */ - -/* -输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。 -要求不能创建任何新的结点,只能调整树中结点指针的指向。 - */ -public class T36 { - public static void main(String[] args) { - int[] pre = {10, 6, 4, 8, 14, 12, 16}; - int[] in = {4, 6, 8, 10, 12, 14, 16}; - TreeNode treeNode = TreeNode.setBinaryTree(pre, in); - TreeNode listNode = Convert(treeNode); - } - - private static TreeNode Convert(TreeNode treeNode) { - if (treeNode == null) return null; - if (treeNode.left == null && treeNode.right == null) return treeNode; - // left - TreeNode left = Convert(treeNode.left); - TreeNode p = left; - while (p != null && p.right != null) { - p = p.right; - } - if (left != null) { - p.right = treeNode; - treeNode.left = p; - } - // right - TreeNode right = Convert(treeNode.right); - if (right != null) { - treeNode.right = right; - right.left = treeNode; - } - return left != null ? left : treeNode; - } -} diff --git a/SwordOffer/src/books/T37.java b/SwordOffer/src/books/T37.java deleted file mode 100644 index bd334bd9..00000000 --- a/SwordOffer/src/books/T37.java +++ /dev/null @@ -1,33 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 序列化二叉树 - * @author: mf - * @create: 2019/09/18 10:02 - */ - -/* -请实现两个函数,分别用来序列化和反序列化二叉树。 - */ -public class T37 { - public static void main(String[] args) { - int[] pre = {1, 2, 4, 3, 5, 6}; - int[] in = {4, 2, 1, 5, 3, 6}; - TreeNode root = TreeNode.setBinaryTree(pre, in); - - serialize(root); - } - - - private static void serialize(TreeNode root) { - if (root == null) { - System.out.print("$,"); - return; - } - System.out.print(root.val + ","); - serialize(root.left); - serialize(root.right); - - } -} diff --git a/SwordOffer/src/books/T38.java b/SwordOffer/src/books/T38.java deleted file mode 100644 index 52245300..00000000 --- a/SwordOffer/src/books/T38.java +++ /dev/null @@ -1,54 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 字符串的排列 - * @author: mf - * @create: 2019/09/19 09:51 - */ - -/* -输入一个字符串,打印出该字符串中字符的所有排列。 -例如,输入字符串abc,则打印出由字符a、b、c所能 -排列出来的所有字符串abc、acb、bca、cab和cba - */ -/* -思路: -每次分为两个部分 -第一部分固定第一个字符,后面的交换 -第二部分继续可以分为两个部分, -第一个字符固定,后面交换 -依次类堆 -递归 - */ -public class T38 { - public static void main(String[] args) { - pemutation("abc"); - } - - private static void pemutation(String s) { - if (s == null) return; - char[] chars = s.toCharArray(); - pemutation(chars, 0); - } - - private static void pemutation(char[] chars, int begin) { - if (chars.length == 0 || begin < 0 || begin > chars.length - 1) return; - if (begin == chars.length - 1) { - String s = new String(chars); - System.out.print(s + '\t'); - } else { - for (int i = begin; i < chars.length; i++) { - swap(chars, begin, i); // 交换 - pemutation(chars, begin + 1); - swap(chars, begin, i); // 交换回去 - } - } - } - - private static void swap(char[] chars, int begin, int i) { - char temp = chars[begin]; - chars[begin] = chars[i]; - chars[i] = temp; - } -} diff --git a/SwordOffer/src/books/T39.java b/SwordOffer/src/books/T39.java deleted file mode 100644 index 27f419b7..00000000 --- a/SwordOffer/src/books/T39.java +++ /dev/null @@ -1,117 +0,0 @@ -package books; - -import java.util.HashMap; -import java.util.Map; - -/** - * @program JavaBooks - * @description: 数组中出现次数超过一半的数字 - * @author: mf - * @create: 2019/09/20 10:01 - */ - -/* -数组中有一个数字出现的次数超过数组长度的一半 -请找出这个数字。例如,输入一个长度为9的数组 -{1,2,3,2,2,2,5,4,2},由于2在数组中出现了5 -次,超过数组长度的一半,因此输出2 - */ -public class T39 { - public static void main(String[] args) { - int[] arr = {1, 2, 3, 2, 2, 2, 5, 4, 2}; -// int res = moreThanHalfNum(arr); -// int res = moreThanHalfNum2(arr); - int res = moreThanHalfNum3(arr); - System.out.println(res); - } - - // 哈希 - private static int moreThanHalfNum3(int[] arr) { - HashMap maps = new HashMap<>(); - for (int i = 0; i < arr.length; i++) { - if (maps.containsKey(arr[i])) { - maps.put(arr[i], maps.get(arr[i]) + 1); - } else { - maps.put(arr[i], 1); - } - } - int length = arr.length >> 1; -// for (Map.Entry entry : maps.entrySet()) { -// if (entry.getValue() > length) { -// return entry.getKey(); -// } -// } - for (Integer key : maps.keySet()) { - if (maps.get(key) > length) { - return key; - } - } - return 0; - } - - // 根据数组特性,相等++, 否则--, 毕竟最后剩最多次数的那个数 - private static int moreThanHalfNum2(int[] arr) { - if (arr == null || arr.length == 0) return 0; - int res = arr[0]; - int times = 1; - for (int i = 1; i < arr.length; i++) { - if (times == 0) { - res = arr[i]; - times = 1; - } else if (arr[i] == res) { - times++; - } else { - times--; - } - } - if (!checkMoreThanHalf(arr, res)) return 0; - - return res; - } - - // 采用快排的partition操作 - // - private static int moreThanHalfNum(int[] arr) { - if (arr == null || arr.length == 0) return 0; - int middle = (arr.length - 1) >> 1; - int partitionIndex = partition(arr, 0, arr.length - 1); - while (partitionIndex != middle) { - if (partitionIndex > middle) { - partitionIndex = partition(arr, 0, partitionIndex - 1); - } else { - partitionIndex = partition(arr, partitionIndex + 1, arr.length - 1); - } - } - int res = arr[middle]; - if (!checkMoreThanHalf(arr, res)) res = 0; - return res; - } - - private static boolean checkMoreThanHalf(int[] arr, int res) { - int times = 0; - for (int i = 0; i < arr.length; i++) { - if (arr[i] == res) times++; - } - boolean isMoreThanHalf = true; - if (times * 2 <= arr.length) isMoreThanHalf = false; - return isMoreThanHalf; - } - - private static int partition(int[] arr, int left, int right) { - int pivot = left; - int index = pivot + 1; - for (int i = index; i <= right; i++) { - if (arr[i] < arr[pivot]) { - swap(arr, i, index++); - } - } - swap(arr, pivot, index - 1); - return index - 1; - } - - private static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} diff --git a/SwordOffer/src/books/T4.java b/SwordOffer/src/books/T4.java deleted file mode 100644 index f4ab7a21..00000000 --- a/SwordOffer/src/books/T4.java +++ /dev/null @@ -1,45 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二维数组中的查找 - * @author: mf - * @create: 2019/08/17 10:42 - */ - - -/* -在一个二维数组中,每一行都按照从左到右递增的顺序排序 -每一列都按照从上到下递增的排序顺序。请完成一个人函数 -输入这样的一个二维数组和一个整数,判断数组中是否含有该函数 - */ -public class T4 { - - public static void main(String[] args) { - int[][] arr = {{1, 2, 8, 9}, - {2, 4, 9, 12}, - {4, 7, 10, 13}, - {6, 8, 11, 15}}; - boolean res = find(arr, 7); - System.out.println(res); - } - - /** - * 数组规律特性,边界问题 - * @param arr - * @param target - * @return - */ - public static boolean find(int[][] arr, int target) { - int row = 0; - // i = 最后一行,往上扫 - for (int i = arr.length - 1; i >= 0; i--) { - // 往右扫 - // 只要小于target,row就++,不满足条件则跳到上一行,保持现在的row - while (row < arr[0].length && arr[i][row] < target) row++; - if (row == arr[0].length) return false;// row 越界 flase - if(arr[i][row] == target) return true; // 判断是否相等 - } - return false; - } -} diff --git a/SwordOffer/src/books/T40.java b/SwordOffer/src/books/T40.java deleted file mode 100644 index 86e1b5ad..00000000 --- a/SwordOffer/src/books/T40.java +++ /dev/null @@ -1,70 +0,0 @@ -package books; - -import java.util.Arrays; - -/** - * @program JavaBooks - * @description: 最小的k个数 - * @author: mf - * @create: 2019/09/23 14:22 - */ - -/* -输入n个整数,找出其中最小的k个数。例如,输入 -4、5、1、6、2、7、3、8这个8个数字,则最小的 -4个数字数1、2、3、4 - */ - -/* -思路: -partition操作 -或者 -最大堆 -或者 -红黑树 - */ -public class T40 { - public static void main(String[] args) { - // partition - int[] arr = {4, 5, 1, 6, 2, 7, 3, 8}; - int[] resArr = getLeastNumbers(arr, 4); - System.out.println(Arrays.toString(resArr)); - - } - - // partition 可修改数组 - private static int[] getLeastNumbers(int[] arr, int k) { - if (arr == null || k < 0) return null; - int index = partition(arr, 0, arr.length - 1); - while (index != k - 1) { - if (index > k -1) { - index = partition(arr, 0, index - 1); - } else { - index = partition(arr, index + 1, arr.length - 1); - } - } - int[] resArr = new int[k]; - for (int i = 0; i < k; i++) { - resArr[i] = arr[i]; - } - return resArr; - } - - private static int partition(int[] arr, int left, int right) { - int pivot = left; - int index = pivot + 1; - for (int i = index; i <= right; i++) { - if (arr[i] < arr[pivot]) { - swap(arr, i, index++); - } - } - swap(arr, pivot, index - 1); - return index - 1; - } - - private static void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} diff --git a/SwordOffer/src/books/T41.java b/SwordOffer/src/books/T41.java deleted file mode 100644 index 89fef9c0..00000000 --- a/SwordOffer/src/books/T41.java +++ /dev/null @@ -1,105 +0,0 @@ -package books; - -import java.security.PrivateKey; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.PriorityQueue; - -/** - * @program JavaBooks - * @description: 数据流中的中位数 - * @author: mf - * @create: 2019/09/24 08:59 - */ - -/* -如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值 -那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中 -读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 - */ - -/* -思路: -先用链表 - */ -public class T41 { - private static int count = 0; - private static PriorityQueue minHeap = new PriorityQueue<>(); - private static PriorityQueue maxHeap = new PriorityQueue<>(15, new Comparator() { - @Override - public int compare(Integer o1, Integer o2) { - return o2 - o1; - } - }); - public static void main(String[] args) { - int[] arr = {1, 3, 2, 4, 5}; - LinkedList list = new LinkedList<>(); - for (int num : arr) { - insert(list, num); - } - Double median = getMedian(list); - System.out.println(median); - - - - // 最大堆最小堆 - for (int num : arr) { - insert(num); - } - Double median2 = getMedian(); - System.out.println(median2); - } - - // 最大堆和最小堆 - private static void insert(Integer num) { - if ((count & 0x1) == 0) { - maxHeap.offer(num); - int filterMaxNum = maxHeap.poll(); - minHeap.offer(filterMaxNum); - } else { - minHeap.offer(num); - int filteredMinNum = minHeap.poll(); - maxHeap.offer(filteredMinNum); - } - count++; - } - - private static Double getMedian() { - if ((count & 0x1) == 0) { - return new Double((minHeap.peek() + maxHeap.peek())) / 2; - } else { - return new Double(minHeap.peek()); - } - } - - // 链表 - private static void insert(LinkedList list, Integer num) { - if (list.size() == 0 || num < list.getFirst()) { - list.add(num); - } else { - boolean insertFlag = false; - for (Integer e : list) { - if (num < e) { - int index = list.indexOf(e); - list.add(index, num); - insertFlag = true; - break; - } - } - if (!insertFlag) { - list.addLast(num); - } - } - } - - // 链表 - private static Double getMedian(LinkedList list) { - if (list.size() == 0) return null; - if ((list.size() & 0x1) == 0) { - int i = list.size() >> 1; - Double a = Double.valueOf(list.get(i - 1) + list.get(i)); - return a / 2; - } - return Double.valueOf(list.get(list.size() / 2)); - } -} diff --git a/SwordOffer/src/books/T42.java b/SwordOffer/src/books/T42.java deleted file mode 100644 index 8c8e2a93..00000000 --- a/SwordOffer/src/books/T42.java +++ /dev/null @@ -1,52 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 连续子数组的最大和 - * @author: mf - * @create: 2019/09/25 09:33 - */ - -/* -输入一个整型数组,数组里有正数也有负数。数组中的 -一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 -要求时间复杂度为o(n)。 - */ -public class T42 { - public static void main(String[] args) { - int[] arr = {1, -2, 3, 10, -4, 7, 2, -5}; - int max = findGreatestSumOfArr(arr); - int max2 = findGreatestSumOfarr2(arr); - System.out.println(max); - System.out.println(max2); - } - // 分析数组规律 - private static int findGreatestSumOfArr(int[] arr) { - if (arr == null || arr.length == 0) return 0; - int nCurNum = 0; - int greatestSum = 0; - for (int i = 0; i < arr.length; i++) { - if (nCurNum <= 0) { - nCurNum = arr[i]; - } else { - nCurNum += arr[i]; - } - if (nCurNum > greatestSum) { - greatestSum = nCurNum; - } - } - return greatestSum; - } - - // 动态规划 感觉和上面的方法异曲同工罢了。。 - private static int findGreatestSumOfarr2(int[] arr) { - if (arr == null || arr.length == 0) return 0; - int res = arr[0]; // 记录当前所有子数组的和的最大值 - int max = arr[0]; // 记录包含arr[i]的连续子数组的和的最大值 - for (int i = 1; i < arr.length; i++) { - max = Math.max(max + arr[i], arr[i]); - res = Math.max(max, res); // 更新最大值 max其实就是nCurNum - } - return res; - } -} diff --git a/SwordOffer/src/books/T43.java b/SwordOffer/src/books/T43.java deleted file mode 100644 index 1c610637..00000000 --- a/SwordOffer/src/books/T43.java +++ /dev/null @@ -1,48 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 1~n整数中1出现的次数 - * @author: mf - * @create: 2019/09/26 09:48 - */ - -/* -输入一个整数n,求1~n这n个整数的十进制表示中1出现的 -次数。例如,输入12,1~12这些整数中包含1的数字有1、10 -、11和12,1一共出现了5次 - */ -public class T43 { - public static void main(String[] args) { -// int count = numberOfBetweenAndN(12); - int count = numberOfBetweenAndN2(12); - System.out.println(count); - } - - private static int numberOfBetweenAndN(int num) { - int count = 0; - for (int i = 0; i <= num; i++) { - String n = String.valueOf(i); - for (int j = 0; j < n.length(); j++) { - if ('1' == n.charAt(j)) { - count++; - } - } - } - return count; - } - - private static int numberOfBetweenAndN2(int num) { - int count = 0; - for (int i = 1; i <= num; i++) { - int n = i; - while (n != 0) { - if (n % 10 == 1) { - count++; - } - n /= 10; - } - } - return count; - } -} diff --git a/SwordOffer/src/books/T44.java b/SwordOffer/src/books/T44.java deleted file mode 100644 index 7d25a5d6..00000000 --- a/SwordOffer/src/books/T44.java +++ /dev/null @@ -1,56 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 数字序列中某一位的数字 - * @author: mf - * @create: 2019/09/27 09:44 - */ - -/* -数字以01234567891011121213141516...的格式序列化到一个字符 -序列中。在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4, -等等。请写一个函数,求任意第n位对应的数字。 - */ -public class T44 { - public static void main(String[] args) { - int res = digitAtIndex(1002); - System.out.println(res); - } - - private static int digitAtIndex(int index) { - if (index < 0) return -1; - int digits = 1; - while (true) { - int numbers = countOfInteger(digits); - if (index < numbers * digits) return digitAtIndex(index, digits); - index -= digits * numbers; - digits++; - } - } - - // 得到m位的数字总共有多少个 - private static int countOfInteger(int digits) { - if (digits == 1) { - return 10; - } - int count = (int) Math.pow(10, digits - 1); - return 9 * count; - } - - private static int digitAtIndex(int index, int digits) { - int number = beginNumber(digits) + index / digits; - int indexFromRight = digits - index % digits; - for (int i = 1; i < indexFromRight; i++) { - number /= 10; - } - return number % 10; - } - - private static int beginNumber(int digits) { - if (digits == 1) { - return 0; - } - return (int) Math.pow(10, digits - 1); - } -} diff --git a/SwordOffer/src/books/T45.java b/SwordOffer/src/books/T45.java deleted file mode 100644 index 4b7ad9ad..00000000 --- a/SwordOffer/src/books/T45.java +++ /dev/null @@ -1,46 +0,0 @@ -package books; - -import java.util.Arrays; -import java.util.Comparator; - -/** - * @program JavaBooks - * @description: 把数组排成最小的数 - * @author: mf - * @create: 2019/09/28 10:18 - */ - -/* -输入一个正整数数组,把数组里所有数字拼接起来排成一个数, -打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则 -打这3个数字能排成的最小数字321323 - */ -public class T45 { - public static void main(String[] args) { - int[] arr = {3, 32, 321}; - String res = printMinNumber(arr); - System.out.println(res); - } - - private static String printMinNumber(int[] arr) { - if (arr == null || arr.length == 0) return ""; - int len = arr.length; - String[] str = new String[len]; - StringBuffer stringBuffer = new StringBuffer(); - for (int i = 0; i < len; i++) { - str[i] = String.valueOf(arr[i]); - } - Arrays.sort(str, new Comparator() { - @Override - public int compare(String o1, String o2) { - String c1 = o1 + o2; - String c2 = o2 + o1; - return c1.compareTo(c2); - } - }); - for (int i = 0; i < len; i++) { - stringBuffer.append(str[i]); - } - return stringBuffer.toString(); - } -} diff --git a/SwordOffer/src/books/T46.java b/SwordOffer/src/books/T46.java deleted file mode 100644 index 7d442862..00000000 --- a/SwordOffer/src/books/T46.java +++ /dev/null @@ -1,61 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 把数字翻译成字符串 - * @author: mf - * @create: 2019/09/29 10:35 - */ - -/* -给定一个数字,我们按照如下规则把它翻译为字符串: -0翻译成”a”,1翻译成”b”,……,11翻译成”l”,……,25翻译成”z”。 -一个数字可能有多个翻译。例如12258有5种不同的翻译, -它们分别是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。请编程实现一个函数用来计算一个数字有多少种不同的翻译方法。 - */ - -/* -思路 -可以选一个数字或两个连续的数字(10~25)翻译成一个字符。 -定义f(i):从第i位数字开始的不同翻译数目 -1)若第i个数字和第i+1个数字拼接成的数字在10~25范围内,则递归式子为: - f(i)=f(i+1)+f(i+2) -2)否则 - f(i)=f(i+1) - */ -public class T46 { - public static void main(String[] args) { - System.out.println(getTranslationCount(12258)); - } - - private static int getTranslationCount(int number) { - if (number < 10) return 0; - return translationCount(String.valueOf(number)); - } - - private static int translationCount(String number) { - int length = number.length(); - int[] countRecords = new int[length]; - // 只有一个数字,则只有一种翻译方式 - countRecords[length - 1] = 1; - int count; - for (int i = length - 2; i >=0; i--) { - count = countRecords[i + 1]; - int digit1 = number.charAt(i) - '0'; - int digit2 = number.charAt(i + 1) - '0'; - int connectNumber = digit1 * 10 + digit2; // 拼接两个数字 - if (connectNumber >= 10 && connectNumber <= 25) { - if (i < length - 2) { - //f(i) = f(i+1) + f(i+2) - count += countRecords[i + 2]; - } else if (i == length - 2) { - count += 1; - } - } - countRecords[i] = count; - } - // - return countRecords[0]; - } -} - diff --git a/SwordOffer/src/books/T47.java b/SwordOffer/src/books/T47.java deleted file mode 100644 index cba66669..00000000 --- a/SwordOffer/src/books/T47.java +++ /dev/null @@ -1,55 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 礼物的最大价值 - * @author: mf - * @create: 2019/09/30 09:36 - */ - -/* -在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。 -你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。 -给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物? - */ - -/* -思路: -使用动态规划,f(i,j)表示到达坐标[i,j]时能拿到的最大礼物总和。 -则当前格子f(i,j)可由左边格子f(i-1,j)或f(i,j-1)上面格子到达。因此,递归式子为: -f(i,j)=max(f(i−1,j),f(i,j−1))+gift[i,j], - -其中,gift[i,j]=坐标[i,j]格子里的礼物 - */ -public class T47 { - public static void main(String[] args) { - int[][] values = { - {1, 2, 3}, - {4, 5, 6}, - {7, 8, 9} - }; - - System.out.println(getMaxPath(values)); - } - - private static int getMaxPath(int[][] values) { - if (values == null) return 0; - int rows = values.length; - if (rows == 0) return 0; - int cols = values[0].length; - if (cols == 0) return 0; - int[][] maxValues = new int[rows][cols]; - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - int fromUp = 0; // 上面 - int fromLeft = 0; // 左边 - if (i > 0) - fromUp = maxValues[i - 1][j]; - if (j > 0) - fromLeft = maxValues[i][j - 1]; - maxValues[i][j] = Math.max(fromLeft, fromUp) + values[i][j]; - } - } - return maxValues[rows - 1][cols - 1]; - } -} diff --git a/SwordOffer/src/books/T48.java b/SwordOffer/src/books/T48.java deleted file mode 100644 index 9483b03e..00000000 --- a/SwordOffer/src/books/T48.java +++ /dev/null @@ -1,50 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 最长不含重复字符的子字符串 - * @author: mf - * @create: 2019/10/01 15:20 - */ - -/* -请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含从'a'到'z'的字符。 - */ - -/* -思路:动态规划 -(1)当第i个字符之前未出现过,则有:f(i)=f(i-1)+1 -(2)当第i个字符之前出现过,记该字符与上次出现的位置距离为d - 1)如果d<=f(i-1),则有f(i)=d; - 2)如果d>f(i-1),则有f(i)=f(i-1)+1; - - */ -public class T48 { - public static void main(String[] args) { - System.out.println(maxLength("arabcacfr")); - } - - private static int maxLength(String str) { - if (str == null || str.length() < 0) return 0; - int preLength = 0; // f(i-1) - int curLength = 0; // f(i) - int maxLength = 0; - int[] pos = new int[26]; - for (int i = 0; i < pos.length; i++) { - pos[i] = -1; - } - for (int i = 0; i < str.length(); i++) { - int letterNumber = str.charAt(i)-'a'; - if(pos[letterNumber]< 0 || i-pos[letterNumber]>preLength) { - curLength=preLength+1; - } else { - curLength=i-pos[letterNumber]; - } - pos[letterNumber]=i; - if(curLength>maxLength) - maxLength=curLength; - preLength=curLength; - } - return maxLength; - } -} diff --git a/SwordOffer/src/books/T49.java b/SwordOffer/src/books/T49.java deleted file mode 100644 index a70466be..00000000 --- a/SwordOffer/src/books/T49.java +++ /dev/null @@ -1,45 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 丑数 - * @author: mf - * @create: 2019/10/02 15:33 - */ -/* -把只包含质因子2、3和5的数称作丑数(Ugly Number)。 -例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 - */ -public class T49 { - public static void main(String[] args) { - System.out.println(GetUglyNumber(10)); - } - - private static int GetUglyNumber(int index) { - if (index <= 0) return 0; - int[] result = new int[index]; - int count = 0; - int i2 = 0; - int i3 = 0; - int i5 = 0; - result[0] = 1; - int temp = 0; - while (count < index - 1) { - temp = min(result[i2] * 2,min(result[i3] * 3,result[i5] * 5)); - if (temp == result[i2] * 2) { - i2++; - } - if (temp == result[i3] * 3) { - i3++; - } - if (temp == result[i5] * 5) { - i5++; - } - result[++count] = temp; - } - return result[index - 1]; - } - private static int min(int a, int b) { - return (a > b) ? b : a; - } -} diff --git a/SwordOffer/src/books/T5.java b/SwordOffer/src/books/T5.java deleted file mode 100644 index 598c6996..00000000 --- a/SwordOffer/src/books/T5.java +++ /dev/null @@ -1,59 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 替换空格 - * @author: mf - * @create: 2019/08/18 18:27 - */ - -/* -请实现一个函数,把字符串中的每个空格替换成"%20",例如 -输入"We are happy.",则输出"We%20are%20happy." - */ -public class T5 { - public static void main(String[] args) { - StringBuffer s = new StringBuffer("We are happy."); -// String s1 = replaceSpace(s); - String s1 = replaceSpace2(s); - System.out.println(s1); - } - - /** - * 双指针,从后往前遍历即可 - * @param str - * @return - */ - public static String replaceSpace(StringBuffer str) { - int spaceNum = 0; - // 检测空格数目 - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == ' ') spaceNum++; - } - - char[] s1 = new char[str.length() + 2 * spaceNum]; // 新数组的长度 - int p2 = s1.length - 1; // 定义新数组的指针 - for (int i = str.length() - 1; i>= 0; i--) { // 从后往前遍历 - if (str.charAt(i) == ' ') { - s1[p2--] = '0'; - s1[p2--] = '2'; - s1[p2--] = '%'; - } else { - s1[p2--] = str.charAt(i); - } - } - return new String(s1); - } - // 第二种方法,Stringbuffer的特性 - public static String replaceSpace2(StringBuffer str) { - StringBuffer s1 = new StringBuffer(); - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == ' ') { - s1.append("%20"); - } else { - s1.append(str.charAt(i)); - } - } - return new String(s1); - } -} diff --git a/SwordOffer/src/books/T50.java b/SwordOffer/src/books/T50.java deleted file mode 100644 index 0e59335b..00000000 --- a/SwordOffer/src/books/T50.java +++ /dev/null @@ -1,39 +0,0 @@ -package books; - -import java.util.HashMap; -import java.util.Map; - -/** - * @program JavaBooks - * @description: 第一个只出现一次的字符 - * @author: mf - * @create: 2019/10/03 09:39 - */ - -/* -在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出b - */ -public class T50 { - public static void main(String[] args) { - Integer first = FirstNotRepeatingChar("abaccdeff"); - System.out.println(first); - } - // 哈希表 - private static int FirstNotRepeatingChar(String str) { - if (str == null || str.length() == 0) return -1; - Map map = new HashMap<>(); - for (int i = 0; i < str.length(); i++) { - if (map.containsKey(str.charAt(i))) { - map.put(str.charAt(i), map.get(str.charAt(i)) + 1); - } else { - map.put(str.charAt(i), 1); - } - } - for (int i = 0; i < str.length(); i++) { - if (map.get(str.charAt(i)) == 1) { - return i; - } - } - return -1; - } -} diff --git a/SwordOffer/src/books/T51.java b/SwordOffer/src/books/T51.java deleted file mode 100644 index 2bfac704..00000000 --- a/SwordOffer/src/books/T51.java +++ /dev/null @@ -1,63 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 数组中的逆序对 - * @author: mf - * @create: 2019/10/04 09:42 - */ - -/* -在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 -输入一个数组,求出这个数组中的逆序对的总数。例如,在数组{7,5,6,4}中,一共存在 -5个逆序对,分别是是(7,6)、(7,5)、(7,4)和(5,4)。 - */ -public class T51 { - private static int count = 0; - public static void main(String[] args) { - // 其实就是归并排序 - int[] arr = {7, 5, 6, 4}; - mergeSort(arr); - System.out.println(count); - } - - private static void mergeSort(int[] arr) { - if (arr == null || arr.length == 0) return; - mergeSort(arr, 0, arr.length - 1); - } - - private static void mergeSort(int[] arr, int left, int right) { - if (left == right) return; - int mid = (left + right) >> 1; - mergeSort(arr, left, mid); - mergeSort(arr, mid + 1, right); - merge(arr, left, mid, right); - } - - private static void merge(int[] arr, int left, int mid, int right) { - int[] help = new int[right - left + 1]; - int i = 0; - int p1 = left; - int p2 = mid + 1; - while (p1 <= mid && p2 <= right) { - if (arr[p1] > arr[p2]) { - help[i++] = arr[p2++]; - // arr[p1] > arr[p2] - // 那么arr[p1]后面的元素都比arr[p2]大 - // 因为有序 - count += mid - p1 + 1; - } else { - help[i++] = arr[p1++]; - } - } - while (p1 <= mid) { - help[i++] = arr[p1++]; - } - while (p2 <= right) { - help[i++] = arr[p2++]; - } - for (int j = 0; j < help.length; j++) { - arr[left + j] = help[j]; - } - } -} diff --git a/SwordOffer/src/books/T52.java b/SwordOffer/src/books/T52.java deleted file mode 100644 index eee6418c..00000000 --- a/SwordOffer/src/books/T52.java +++ /dev/null @@ -1,54 +0,0 @@ -package books; - -import java.util.HashMap; - -/** - * @program JavaBooks - * @description: 两个链表的第一个公共节点 - * @author: mf - * @create: 2019/10/05 13:11 - */ - -/* -输入两个链表,找出它们的第一个公共节点。 - */ -public class T52 { - public static void main(String[] args) { - ListNode listNode = new ListNode(1); - ListNode listNode1 = new ListNode(2); - - ListNode listNode2 = new ListNode(3); - ListNode listNode3 = new ListNode(4); - ListNode listNode4 = new ListNode(5); - - listNode.next = listNode2; - listNode2.next = listNode3; - listNode3.next = listNode4; - - listNode1.next = listNode3; - - ListNode commonNode = findFirstCommonNode(listNode, listNode1); - System.out.println(commonNode.value); - } - - // 哈希 - private static ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) { - if (pHead1 == null || pHead2 == null) return null; - ListNode cur1 = pHead1; - ListNode cur2 = pHead2; - HashMap map = new HashMap<>(); - while (cur1 != null) { - map.put(cur1, 1); - cur1 = cur1.next; - } - while (cur2 != null) { - if (map.containsKey(cur2)) { - return cur2; - } - cur2 = cur2.next; - } - return null; - } - - -} diff --git a/SwordOffer/src/books/T53.java b/SwordOffer/src/books/T53.java deleted file mode 100644 index 82300a1a..00000000 --- a/SwordOffer/src/books/T53.java +++ /dev/null @@ -1,28 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 在排序数组中查找数字 - * @author: mf - * @create: 2019/10/06 10:01 - */ - -/* -统计一个数字在排序数组中出现的次数。例如,输入排序数组{1,2,3,3,3,3,4,5} -和数字3,由于3在这个数组中出现了4次,因此输出4。 - */ -public class T53 { - public static void main(String[] args) { - int[] arr = {1,2,3,3,3,3,4,5}; - System.out.println(getArrayOfK(arr, 3)); - } - - private static int getArrayOfK(int[] arr, int k) { - if (arr == null || arr.length == 0) return 0; - int count = 0; - for (int i = 0; i < arr.length; i++) { - if (arr[i] == k) count++; - } - return count; - } -} diff --git a/SwordOffer/src/books/T54.java b/SwordOffer/src/books/T54.java deleted file mode 100644 index 2480b33f..00000000 --- a/SwordOffer/src/books/T54.java +++ /dev/null @@ -1,52 +0,0 @@ -package books; - -import java.util.ArrayList; - -/** - * @program JavaBooks - * @description: 二叉搜索树的第K大节点 - * @author: mf - * @create: 2019/10/06 10:14 - */ - -/* -如书T54 - */ -public class T54 { - public static void main(String[] args) { - int[] pre = {5,3,2,4,7,6,8}; - int[] in = {2,3,4,5,6,7,8}; - TreeNode pNode = TreeNode.setBinaryTree(pre, in); - TreeNode kNode = kthNode(pNode, 3); - System.out.println(kNode.val); - } - - private static TreeNode kthNode(TreeNode pNode, int k) { - ArrayList arr = new ArrayList<>(); - if (pNode == null || k == 0) { - return null; - } - arr = inOrder(arr, pNode); - TreeNode tr = pNode; - if (k <= arr.size()) { - tr = arr.get(k - 1); - } - - return tr; - } - - private static ArrayList inOrder(ArrayList arr, TreeNode root) { - - if(root ==null){ - return null; - } - if(root.left != null){ - inOrder(arr, root.left); - } - arr.add(root); - if(root.right != null){ - inOrder(arr, root.right); - } - return arr; - } -} diff --git a/SwordOffer/src/books/T55.java b/SwordOffer/src/books/T55.java deleted file mode 100644 index a0d928b0..00000000 --- a/SwordOffer/src/books/T55.java +++ /dev/null @@ -1,32 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二叉树的深度 - * @author: mf - * @create: 2019/10/08 09:38 - */ - -/* -输入一颗二叉树的根节点,求该树的深度。从根节点到叶节点 -依次经过的节点(含根、叶节点)形成树的一条路径,最长路径 -的长度为树的深度。 - */ - -public class T55 { - public static void main(String[] args) { - int[] pre = {1, 2, 4, 5, 7, 3, 6}; - int[] in = {4, 2, 7, 5, 1, 3, 6}; - TreeNode pNode = TreeNode.setBinaryTree(pre, in); - System.out.println(treeDepth(pNode)); - } - - // 递归 - private static int treeDepth(TreeNode pNode) { - if (pNode == null) - return 0; - int pLeft = treeDepth(pNode.left); - int pRight = treeDepth(pNode.right); - return pLeft > pRight ? (pLeft + 1) : (pRight + 1); - } -} diff --git a/SwordOffer/src/books/T56.java b/SwordOffer/src/books/T56.java deleted file mode 100644 index 07960f59..00000000 --- a/SwordOffer/src/books/T56.java +++ /dev/null @@ -1,66 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 数组中只出现一次的两个数字 - * @author: mf - * @create: 2019/10/08 10:10 - */ -/* -一个整型数组里除两个数字之外,其他数字都出现了两次。 -请写程序找出这两个只出现一次的数字。要求时间复杂度是o(n), -空间复杂度是o(1) - */ -public class T56 { - public static void main(String[] args) { - int[] arr = {2, 4, 3, 6, 3, 2, 5, 5,}; - int[] num1 = new int[1]; - int[] num2 = new int[1]; - findNumsAppearOnce(arr, num1, num2); - System.out.println(num1[0] + "..." + num2[0]); - } - - private static void findNumsAppearOnce(int[] arr, int[] num1, int[] num2) { - if (arr == null || arr.length < 2) return; - int exorRes = 0; - for (int num : arr) { - exorRes ^= num; - } - System.out.println(exorRes); - //从右向左找到第一个为1的位的位置 - int firstOneIndex = findFirstOneBit(exorRes); - for (int num : arr) { - // 分成两个子数组 - if (isOneBitAt(firstOneIndex, num)) { - num1[0] ^= num; - } else { - num2[0] ^= num; - } - } - } - - /** - * bitIndex处的位数是否为1 - * @param bitIndex - * @param num - * @return - */ - private static boolean isOneBitAt(int bitIndex, int num) { - num = num >> bitIndex; - return (num & 1) == 1; - } - - /** - * 从右向左找到第一个为1的位的位置 - * @param exorRes - * @return - */ - private static int findFirstOneBit(int exorRes) { - int oneBitIndex = 0; - while ((exorRes & 1) == 0 && oneBitIndex < 32) { - exorRes = exorRes >> 1; - oneBitIndex++; - } - return oneBitIndex; - } -} diff --git a/SwordOffer/src/books/T57.java b/SwordOffer/src/books/T57.java deleted file mode 100644 index af3a9fc3..00000000 --- a/SwordOffer/src/books/T57.java +++ /dev/null @@ -1,64 +0,0 @@ -package books; - -import java.util.ArrayList; - -/** - * @program JavaBooks - * @description: 和为s的数字 - * @author: mf - * @create: 2019/10/08 15:21 - */ - -/* -输入一个递增排序的数组和一个数字s,在数组中查找两个树,使得 -它们的和正好是s。如果有多对数字的和等于s,则输入任意一对即可。 - */ -public class T57 { - public static void main(String[] args) { - int[] arr = {1, 2, 4, 7, 11, 15}; - ArrayList lists = sumToS(arr, 15); - System.out.println(lists.toString()); - } - - private static ArrayList sumToS(int[] arr, int target) { - if (arr == null || arr.length < 2) return null; - ArrayList list = new ArrayList<>(); - int p1 = 0; - int p2 = arr.length - 1; - while (p1 != p2) { - if (arr[p1] + arr[p2] < target) { - p1++; - } else if(arr[p1] + arr[p2] > target) { - p2--; - } else { - list.add(arr[p1]); - list.add(arr[p2]); - return list; - } - } - return null; - } - - private static ArrayList> findContinuousSequence(int sum) { - ArrayList> arrayLists = new ArrayList<>(); - int phigh = 2, plow = 1; - while (phigh > plow) { - int cur = (phigh + plow) * (phigh - plow + 1) / 2; - if (cur < sum) { - phigh++; - } - if (cur == sum) { - ArrayList arrayList = new ArrayList<>(); - for (int i = plow; i <= phigh; i++) { - arrayList.add(i); - } - arrayLists.add(arrayList); - plow++; - } - if (cur > sum) { - plow++; - } - } - return arrayLists; - } -} diff --git a/SwordOffer/src/books/T58.java b/SwordOffer/src/books/T58.java deleted file mode 100644 index 81671a3e..00000000 --- a/SwordOffer/src/books/T58.java +++ /dev/null @@ -1,31 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 翻转字符串 - * @author: mf - * @create: 2019/10/08 15:39 - */ - -/* -输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变, -为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student." -则输出"student. a am I" - */ -public class T58 { - public static void main(String[] args) { - String s = "I am a student."; - System.out.println(reverseSentence(s)); - } - - private static String reverseSentence(String s) { - if (s == null) return null; - if (s.trim().equals("")) return s; - String[] strs = s.split(" "); - StringBuffer sb = new StringBuffer(); - for (int i = strs.length - 1; i >= 0; i--) { - sb.append(strs[i]).append(" "); - } - return sb.substring(0, sb.length() - 1); - } -} diff --git a/SwordOffer/src/books/T59.java b/SwordOffer/src/books/T59.java deleted file mode 100644 index b953f48c..00000000 --- a/SwordOffer/src/books/T59.java +++ /dev/null @@ -1,44 +0,0 @@ -package books; - -import java.util.ArrayList; -import java.util.LinkedList; - -/** - * @program JavaBooks - * @description: 滑动窗口的最大值 - * @author: mf - * @create: 2019/10/08 16:02 - */ - -/* -给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。 -例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小为3, -那么一共存在6个滑动窗口,它们的最大值分别为{4,4,6,6,6,5} - */ -public class T59 { - public static void main(String[] args) { - int[] arr = {2, 3, 4, 2, 6, 2, 5, 1}; - System.out.println(maxInWindow(arr, 3)); - } - - private static ArrayList maxInWindow(int[] arr, int size) { - if (arr == null || arr.length == 0 || size > arr.length || size == 0) return null; - ArrayList res = new ArrayList<>(); - LinkedList queue = new LinkedList<>(); - for (int i = 0; i < arr.length; i++) { - if (! queue.isEmpty()) { - if (i >= queue.peek() + size) { - queue.pop(); - } - while (!queue.isEmpty() && arr[i] > arr[queue.getLast()]) { - queue.removeLast(); - } - } - queue.add(i); - if (i + 1 >= size) { - res.add(arr[queue.peek()]); // - } - } - return res; - } -} diff --git a/SwordOffer/src/books/T6.java b/SwordOffer/src/books/T6.java deleted file mode 100644 index 7efc4f6d..00000000 --- a/SwordOffer/src/books/T6.java +++ /dev/null @@ -1,90 +0,0 @@ -package books; - -import java.util.ArrayList; -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 从尾到头打印链表 - * @author: mf - * @create: 2019/08/19 13:19 - */ - -/* -输入一个链表的头节点,从尾到头反过来打印出每个节点的值。 -链表定义如ListNote - */ - -public class T6 { - ArrayList arrayList = new ArrayList<>(); - public static void main(String[] args) { - - // 递归方法中的arraylist - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - - listNode1.next = listNode2; - listNode2.next = listNode3; - - // 栈方法 - ArrayList list = printListFromTailToHead1(listNode1); - list.forEach(System.out::println); - - // 递归方法 - - } - - /** - * 栈方法 - * @param listNote - * @return - */ - public static ArrayList printListFromTailToHead1(ListNode listNote) { - Stack stack = new Stack<>(); - // 压栈 - while (listNote != null) { - stack.push(listNote.value); - listNote = listNote.next; - } - - // 弹栈 - ArrayList arrayList = new ArrayList<>(); - while (!stack.isEmpty()) { - arrayList.add(stack.pop()); - } - return arrayList; - } - - /** - * 递归方法 - * @param listNote - * @return - */ - public ArrayList printListFromTailToHead2(ListNode listNote) { - if (listNote != null) { - this.printListFromTailToHead2(listNote.next); // 层层递归到最后链表最后一位,从最后一位add - arrayList.add(listNote.value); - } - return arrayList; - } - - -} - - - - -class TestMethod2 { - public static void main(String[] args) { - ListNode listNode1 = new ListNode(1); - ListNode listNode2 = new ListNode(2); - ListNode listNode3 = new ListNode(3); - - listNode1.next = listNode2; - listNode2.next = listNode3; - T6 t6 = new T6(); - ArrayList list = t6.printListFromTailToHead2(listNode1); - list.forEach(System.out::println); - } -} \ No newline at end of file diff --git a/SwordOffer/src/books/T60.java b/SwordOffer/src/books/T60.java deleted file mode 100644 index 6bfea98b..00000000 --- a/SwordOffer/src/books/T60.java +++ /dev/null @@ -1,54 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: n个骰子的点数 - * @author: mf - * @create: 2019/10/08 20:30 - */ - -/* -把n个骰子仍在地上,所有骰子朝上一面的点数之和为s。 -输入n,打印出s的所有可能的值出现的概率。 - */ -public class T60 { - public static void main(String[] args) { - // 基于循环 - PrintProbability(2); - } - - private static void PrintProbability(int number) { - if (number < 1) return; - int[][] pProbabilities = new int[2][6 * number + 1]; - // 初始化数组 - for (int i = 0; i < 6; i++) { - pProbabilities[0][i] = 0; - pProbabilities[1][i] = 0; - } - int flag = 0; - for (int i = 1; i <= 6; i++) { - pProbabilities[flag][i] = 1; //当第一次抛掷骰子时,有6种可能,每种可能出现一次 - } - //从第二次开始掷骰子,假设第一个数组中的第n个数字表示骰子和为n出现的次数, - //在下一循环中,我们加上一个新骰子,此时和为n的骰子出现次数应该等于上一次循环中骰子点数和为n-1,n-2,n-3,n-4,n-5, - //n-6的次数总和,所以我们把另一个数组的第n个数字设为前一个数组对应的n-1,n-2,n-3,n-4,n-5,n-6之和 - for (int k = 2; k <= number; k++) { - for (int i = 0; i < k; i++) { - //第k次掷骰子,和最小为k,小于k的情况是不可能发生的!所以另不可能发生的次数设置为0! - pProbabilities[1-flag][i] = 0; - } - for(int i = k;i <= 6 * k; i++) { - pProbabilities[1-flag][i] = 0;//初始化,因为这个数组要重复使用,上一次的值要清0 - for(int j = 1;j <= i && j <= 6; j++) { - pProbabilities[1-flag][i] += pProbabilities[flag][i-j]; - } - } - flag = 1-flag; - } - double total = Math.pow(6, number); - for(int i = number; i <= 6 * number; i++) { - double ratio = pProbabilities[flag][i]/total; - System.out.println("sum: "+i+" ratio: "+ratio); - } - } -} diff --git a/SwordOffer/src/books/T61.java b/SwordOffer/src/books/T61.java deleted file mode 100644 index 47a0cbee..00000000 --- a/SwordOffer/src/books/T61.java +++ /dev/null @@ -1,46 +0,0 @@ -package books; - -import java.util.Arrays; - -/** - * @program JavaBooks - * @description: 扑克牌中的顺子 - * @author: mf - * @create: 2019/10/08 21:53 - */ - -/* -从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的 -2~10为数字本身,A为1,J为11,Q为12,k为13,而大、小王可以 -看成任意数字。 - */ -public class T61 { - public static void main(String[] args) { - int[] arr = {2, 3, 4, 6, 6}; - System.out.println(isContinuous(arr)); - } - - private static boolean isContinuous(int[] arr) { - int numberOfZero = 0; - int numOfInterval = 0; - int length = arr.length; - if (length == 0) return false; - Arrays.sort(arr); - for (int i = 0; i < length - 1; i++) { - // 大小王 - if (arr[i] == 0) { - numberOfZero++; - continue; - } - // 对子直接返回 - if (arr[i] == arr[i + 1]) { - return false; - } - numOfInterval += arr[i + 1] - arr[i] - 1; - } - if (numberOfZero >= numOfInterval) { - return true; - } - return false; - } -} diff --git a/SwordOffer/src/books/T62.java b/SwordOffer/src/books/T62.java deleted file mode 100644 index 55d985f6..00000000 --- a/SwordOffer/src/books/T62.java +++ /dev/null @@ -1,35 +0,0 @@ -package books; - -import java.util.ArrayList; - -/** - * @program JavaBooks - * @description: 圆圈中最后剩下的数字 - * @author: mf - * @create: 2019/10/09 16:05 - */ - -/* -0,1,...,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里 -删除第m个数字。求出这个圆圈里剩下的最后一个数字。 - */ -public class T62 { - public static void main(String[] args) { - System.out.println(lastRemaining(5, 3)); - } - - private static int lastRemaining(int n, int m) { - if (n == 0 || m == 0) return -1; - ArrayList data = new ArrayList<>(); - for (int i = 0; i < n; i++) { - data.add(i); - } - int index = -1; - while (data.size() > 1) { - index = (index + m) % data.size(); - data.remove(index); - index--; - } - return data.get(0); - } -} diff --git a/SwordOffer/src/books/T63.java b/SwordOffer/src/books/T63.java deleted file mode 100644 index d40ff6f8..00000000 --- a/SwordOffer/src/books/T63.java +++ /dev/null @@ -1,33 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 股票的最大利润 - * @author: mf - * @create: 2019/10/09 16:18 - */ - -/* -假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票 -一次可能获得的最大利润是多少?例如,一只股票在某些时间节点的价格为 -{9, 11, 8, 5, 7, 12, 16, 14}。如果我们能在价格为5的时候买入并 -在价格为16时卖出,则能收获最大的利润是11。 - */ -public class T63 { - public static void main(String[] args) { - int[] arr = {9, 11, 8, 5, 7, 12, 16, 14}; - System.out.println(getMaxDiff(arr)); - } - - private static int getMaxDiff(int[] arr) { - if (arr == null || arr.length == 0) return 0; - int min = arr[0]; - int maxDiff = arr[1] - min; - for (int i = 2; i < arr.length; i++) { - min = arr[i - 1] < min ? arr[i - 1] : min; - int currentDiff = arr[i] - min; - maxDiff = currentDiff > maxDiff ? currentDiff : maxDiff; - } - return maxDiff; - } -} diff --git a/SwordOffer/src/books/T64.java b/SwordOffer/src/books/T64.java deleted file mode 100644 index 844640f3..00000000 --- a/SwordOffer/src/books/T64.java +++ /dev/null @@ -1,25 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 求1+2+...+n - * @author: mf - * @create: 2019/10/09 16:29 - */ - -/* -求1+2+3+...+n,要求不能使用乘除法、 -for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 - */ -public class T64 { - public static void main(String[] args) { - System.out.println(sum(10)); - } - - private static int sum(int n) { - int res = n; - boolean t = ((res != 0) && ((res += sum( n - 1)) != 0)); - return res; - } - -} diff --git a/SwordOffer/src/books/T65.java b/SwordOffer/src/books/T65.java deleted file mode 100644 index d4b3e1bc..00000000 --- a/SwordOffer/src/books/T65.java +++ /dev/null @@ -1,27 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 不用加减乘除做加法 - * @author: mf - * @create: 2019/10/09 19:36 - */ - -/* -写一个函数,求两个整数之和,要求在函数体内不得使用"+","-", -"x","+"四则运算符号。 - */ -public class T65 { - public static void main(String[] args) { - System.out.println(add(2, 3)); - } - - private static int add(int num1, int num2) { - while (num2 != 0) { - int temp = num1 ^ num2; - num2 = (num1 & num2) << 1; - num1 = temp; - } - return num1; - } -} diff --git a/SwordOffer/src/books/T66.java b/SwordOffer/src/books/T66.java deleted file mode 100644 index 8b195f06..00000000 --- a/SwordOffer/src/books/T66.java +++ /dev/null @@ -1,41 +0,0 @@ -package books; /** - * @program JavaBooks - * @description: 构建乘积数组 - * @author: mf - * @create: 2019/10/09 20:09 - */ - -import java.util.Arrays; - -/** - * 给定一个数组A[0,1,...,n-1], - * 请构建一个数组B[0,1,...,n-1], - * 其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。 - * 不能使用除法。 - */ -public class T66 { - public static void main(String[] args) { - int[] arr = {1, 2, 3, 4, 5}; - int[] res = multiply(arr); - System.out.println(Arrays.toString(res)); - } - - private static int[] multiply(int[] arr) { - int length = arr.length; - int[] temp = new int[length]; - if (length != 0) { - temp[0] = 1; - // 计算下三角 - for (int i = 1; i < length; i++) { - temp[i] = temp[i - 1] * arr[i - 1]; - } - int t = 1; - // 计算上三角 - for (int i = length - 2; i >= 0; i--) { - t *= arr[i + 1]; - temp[i] *= t; - } - } - return temp; - } -} diff --git a/SwordOffer/src/books/T7.java b/SwordOffer/src/books/T7.java deleted file mode 100644 index ac29ab49..00000000 --- a/SwordOffer/src/books/T7.java +++ /dev/null @@ -1,57 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 重建二叉树 - * @author: mf - * @create: 2019/08/20 14:38 - */ - -/* -输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。 -假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 -例如,输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和 -中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则如下二叉树并 -输出它的头节点。 - 1 - 2 3 - 4 5 6 - 7 8 - */ -public class T7 { - // 递归 - public TreeNode reConstructBinaryTree(int[] pre, int[] in) { - TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); - return root; - } - - //前序遍历{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6} - private TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) { - - if (startPre > endPre || startIn > endIn) { - return null; - } - TreeNode root = new TreeNode(pre[startPre]); - for (int i = startIn; i <= endIn; i++) { - if (in[i] == pre[startPre]) { - //注意边界,递归可看成重复的子问题。 - root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1); - root.right = reConstructBinaryTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn); - } - } - return root; - } -} - - - - -class TestT7 { - public static void main(String[] args) { - int[] pre = {1, 2, 4, 7, 3, 5, 6, 8}; - int[] in = {4, 7, 2, 1, 5, 3, 8, 6}; - T7 t7 = new T7(); - TreeNode root = t7.reConstructBinaryTree(pre, in); - System.out.println(root.val); - } -} \ No newline at end of file diff --git a/SwordOffer/src/books/T9.java b/SwordOffer/src/books/T9.java deleted file mode 100644 index 3ccd0b47..00000000 --- a/SwordOffer/src/books/T9.java +++ /dev/null @@ -1,74 +0,0 @@ -package books; - -import java.util.PriorityQueue; -import java.util.Stack; - -/** - * @program JavaBooks - * @description: 用两个栈实现队列 - * @author: mf - * @create: 2019/08/21 14:18 - */ - -/* -用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 -appendTail和deleteHead,分别完成在队列尾部插入节点和队列 -头部删除节点的功能。 - */ -public class T9 { - // 定义两个栈 - Stack stack1 = new Stack(); - Stack stack2 = new Stack(); - - public void push(int node) { - stack1.push(node); - } - - public int pop() { - // 倒入stack2 - while (!stack1.isEmpty()) { - stack2.push(stack1.pop()); - } - // 取stack2栈顶 - int first = stack2.pop(); - // 从stack2倒回去 - while (!stack2.isEmpty()) { - stack1.push(stack2.pop()); - } - return first; - } - - public int pop2() { - if (stack2.isEmpty()) { - while (!stack1.isEmpty()) { - stack2.push(stack1.pop()); - } - } - return stack2.pop(); // 返回输出栈的栈顶 - } -} - -/* -用两个队列实现一个栈 - */ -class T9_1 { - PriorityQueue queue1 = new PriorityQueue<>(); - PriorityQueue queue2 = new PriorityQueue<>(); - - public void add(int node) { - queue1.add(node); - } - - public int poll() { - int first = 0; - while (!queue1.isEmpty()) { - first = queue1.poll(); - queue2.add(first); - } - queue2.remove(first); - while(!queue2.isEmpty()) { - queue1.add(queue2.poll()); - } - return first; - } -} \ No newline at end of file diff --git a/SwordOffer/src/books/TreeNode.java b/SwordOffer/src/books/TreeNode.java deleted file mode 100644 index c2eed9cd..00000000 --- a/SwordOffer/src/books/TreeNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package books; - -/** - * @program JavaBooks - * @description: 二叉树 - * @author: mf - * @create: 2019/09/06 10:05 - */ - -public class TreeNode { - int val; - TreeNode left; - TreeNode right; - - public TreeNode(int val) { - this.val = val; - } - - // 给定一个前序和中序数组,生成一颗二叉树 // 根据T7笔试题 - public static TreeNode setBinaryTree(int[] pre, int[] in) { - TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); - return root; - } - - private static TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) { - - if (startPre > endPre || startIn > endIn) { - return null; - } - TreeNode root = new TreeNode(pre[startPre]); - for (int i = startIn; i <= endIn; i++) { - if (in[i] == pre[startPre]) { - root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1); - root.right = reConstructBinaryTree(pre, i - startIn + startPre + 1, endPre, in, i + 1, endIn); - } - } - return root; - } - - // 递归打印前序 - public static void preOrderRe(TreeNode root) { - System.out.println(root.val); - TreeNode leftNode = root.left; - if (leftNode != null) { - preOrderRe(leftNode); - } - - TreeNode rightNode = root.right; - if (rightNode != null) { - preOrderRe(rightNode); - } - } - // 递归打印中序 - public static void midOrderRe(TreeNode node) { - if (node == null) { - return; - } else { - midOrderRe(node.left); - System.out.println(node.val); - midOrderRe(node.right); - } - } - - // 递归打印后序 - public static void postOrderRe(TreeNode node) { - if (node == null) { - return; - } else { - postOrderRe(node.left); - postOrderRe(node.right); - System.out.println(node.val); - } - } - -} diff --git a/SwordOffer/src/web/ListNode.java b/SwordOffer/src/web/ListNode.java deleted file mode 100644 index e002febc..00000000 --- a/SwordOffer/src/web/ListNode.java +++ /dev/null @@ -1,19 +0,0 @@ -package web; - -/** - * @program LeetNiu - * @description: 链表节点 - * @author: mf - * @create: 2020/01/08 16:18 - */ - -public class ListNode { - - int val; - - ListNode next = null; - - public ListNode(int val) { - this.val = val; - } -} diff --git a/SwordOffer/src/web/RandomListNode.java b/SwordOffer/src/web/RandomListNode.java deleted file mode 100644 index a4b2cd88..00000000 --- a/SwordOffer/src/web/RandomListNode.java +++ /dev/null @@ -1,21 +0,0 @@ -package web; - -/** - * @program LeetNiu - * @description: 随机链表 - * @author: mf - * @create: 2020/01/13 13:11 - */ - -public class RandomListNode { - - int label; - - RandomListNode next = null; - - RandomListNode random = null; - - public RandomListNode(int label) { - this.label = label; - } -} diff --git a/SwordOffer/src/web/T1.java b/SwordOffer/src/web/T1.java deleted file mode 100644 index ea93b11b..00000000 --- a/SwordOffer/src/web/T1.java +++ /dev/null @@ -1,26 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二维数组中的查找 - * @author: mf - * @create: 2020/01/06 14:57 - */ - -/** - * 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序, - * 每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数, - * 判断数组中是否含有该整数。 - * - * 思路: - * 注意数组值的规律,可适当举个例子 - */ -public class T1 { - public boolean Find(int target, int [][] array) { - int col = 0; - for (int i = array.length - 1; i >= 0; i--) { - while (col < array[0].length && array[i][col] < target) col++; - if (col == array[0].length) return false; - if (array[i][col] == target) return true; - } - return false; - } -} diff --git a/SwordOffer/src/web/T10.java b/SwordOffer/src/web/T10.java deleted file mode 100644 index 0d1b871c..00000000 --- a/SwordOffer/src/web/T10.java +++ /dev/null @@ -1,24 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 矩形覆盖 - * @author: mf - * @create: 2020/01/09 13:36 - */ - -/** - * 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? - */ -public class T10 { - public int RectCover(int target) { - // 条件 - if (target <= 2) return target; - int pre2 = 1, pre1 = 2; - int sum = 0; - for (int i = 3; i <= target; i++) { - sum = pre2 + pre1; - pre2 = pre1; - pre1 = sum; - } - return sum; - } -} diff --git a/SwordOffer/src/web/T11.java b/SwordOffer/src/web/T11.java deleted file mode 100644 index f69d9977..00000000 --- a/SwordOffer/src/web/T11.java +++ /dev/null @@ -1,20 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二进制1的个数 - * @author: mf - * @create: 2020/01/09 13:41 - */ - -/** - * 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 - */ -public class T11 { - public int NumberOf1(int n) { - int count = 0; - while ( n!= 0) { - count++; - n = (n - 1) & n; - } - return count; - } -} diff --git a/SwordOffer/src/web/T12.java b/SwordOffer/src/web/T12.java deleted file mode 100644 index f0175e1c..00000000 --- a/SwordOffer/src/web/T12.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数值的整数次方 - * @author: mf - * @create: 2020/01/10 09:47 - */ - -/** - * 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 - * - * 保证base和exponent不同时为0 - */ -public class T12 { - public double Power(double base, int exponent) { - // 还是先判断特殊情况 - if (exponent == 0) return 1; - double ans = 1; - boolean flag = false; // 判断倒数 - if (exponent < 0) { - flag = true; - exponent = -exponent; - } - for (int i = 1; i <= exponent; i++) { - ans *= base; - } - if (flag) { - ans = 1 / ans; - } - return ans; - } -} diff --git a/SwordOffer/src/web/T13.java b/SwordOffer/src/web/T13.java deleted file mode 100644 index 1f9ebdd0..00000000 --- a/SwordOffer/src/web/T13.java +++ /dev/null @@ -1,37 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 调整数组顺序使奇数位于偶数前面 - * @author: mf - * @create: 2020/01/10 14:03 - */ - -/** - * 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分, - * 所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 - */ -public class T13 { - - public void reOrderArray(int [] array) { - // 判断 - if (array == null || array.length == 0) return; - for (int i = 0; i < array.length; i++) { - for (int j = 0; j < array.length - 1; j++) { - if ((array[j] & 0x1) == 0 && (array[j + 1] & 0x1) == 1) { - swap(array, j, j + 1); - } - } - } - } - - /** - * 数据交换 - * @param arr - * @param x - * @param y - */ - private void swap(int[] arr, int x, int y) { - int temp = arr[x]; - arr[x] = arr[y]; - arr[y] = temp; - } -} diff --git a/SwordOffer/src/web/T14.java b/SwordOffer/src/web/T14.java deleted file mode 100644 index 50c08584..00000000 --- a/SwordOffer/src/web/T14.java +++ /dev/null @@ -1,63 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 链表中倒数第k个结点 - * @author: mf - * @create: 2020/01/10 15:11 - */ - -import java.util.Stack; - -/** - * 输入一个链表,输出该链表中倒数第k个结点。 - */ -public class T14 { - /** - * 双指针 - * @param head - * @param k - * @return - */ - public ListNode FindKthToTail(ListNode head,int k) { - // 判断 - if (head == null || k <= 0) return null; - ListNode pNode = head; - ListNode kNode = head; - int p1 = 0; - while (pNode != null) { - if (p1 > k - 1){ - kNode = kNode.next; - } - pNode = pNode.next; - p1++; - } - if (p1 >= k) { - return kNode; - } - return null; - } - - /** - * 栈 - * @param head - * @param k - * @return - */ - public ListNode FindKthToTail2(ListNode head,int k) { - // 判断 - if (head == null || k <= 0) return null; - Stack stack = new Stack<>(); - while (head != null) { - stack.push(head); - head = head.next; - } - int temp = 0; - while (!stack.empty()) { - ListNode listNode = stack.pop(); - temp++; - if (temp == k) { - return listNode; - } - } - return null; - } -} diff --git a/SwordOffer/src/web/T15.java b/SwordOffer/src/web/T15.java deleted file mode 100644 index 8f6e5006..00000000 --- a/SwordOffer/src/web/T15.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 反转链表 - * @author: mf - * @create: 2020/01/10 15:21 - */ - -/** - * 输入一个链表,反转链表后,输出新链表的表头。 - */ -public class T15 { - - public ListNode ReverseList(ListNode head) { - // 判断 - if (head == null) return null; - return reverse(null, head); - } - - /** - * 递归 - * @param pre - * @param cur - * @return - */ - private ListNode reverse(ListNode pre, ListNode cur) { - if (cur == null) return pre; - ListNode next = cur.next; - cur.next = pre; - return reverse(cur, next); - } -} diff --git a/SwordOffer/src/web/T16.java b/SwordOffer/src/web/T16.java deleted file mode 100644 index 4e8e62f5..00000000 --- a/SwordOffer/src/web/T16.java +++ /dev/null @@ -1,23 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 合并两个排序的链表 - * @author: mf - * @create: 2020/01/10 15:27 - */ - -/** - * 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 - */ -public class T16 { - public ListNode Merge(ListNode list1,ListNode list2) { - if (list1 == null) return list2; - if (list2 == null) return list1; - if (list1.val < list2.val) { - list1.next = Merge(list1.next, list2); - return list1; - } else { - list2.next = Merge(list1, list2.next); - return list2; - } - } -} diff --git a/SwordOffer/src/web/T17.java b/SwordOffer/src/web/T17.java deleted file mode 100644 index 56223256..00000000 --- a/SwordOffer/src/web/T17.java +++ /dev/null @@ -1,41 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 树的子结构 - * @author: mf - * @create: 2020/01/10 16:07 - */ - -/** - * 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) - */ -public class T17 { - - public boolean HasSubtree(TreeNode root1,TreeNode root2) { - // 判断 - if (root1 == null || root2 == null) return false; - boolean result = false; - if (root1.val == root2.val) { - result = goOnFind(root1, root2); - } - if (!result) { - result = HasSubtree(root1.left, root2); - } - if (!result) { - result = HasSubtree(root1.right, root2); - } - return result; - } - - /** - * 递归找 - * @param root1 - * @param root2 - * @return - */ - private boolean goOnFind(TreeNode root1, TreeNode root2) { - if (root2 == null) return true; - if (root1 == null) return false; - if (root1.val != root2.val) return false; - return goOnFind(root1.left, root2.left) && goOnFind(root1.right, root2.right); - } -} diff --git a/SwordOffer/src/web/T18.java b/SwordOffer/src/web/T18.java deleted file mode 100644 index 7172a184..00000000 --- a/SwordOffer/src/web/T18.java +++ /dev/null @@ -1,29 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉树的镜像 - * @author: mf - * @create: 2020/01/10 16:18 - */ - -/** - * 操作给定的二叉树,将其变换为源二叉树的镜像。 - */ -public class T18 { - public void Mirror(TreeNode root) { - // 判断 - if (root == null) return; - if (root.left == null && root.right == null) return; - // 交换 - swap(root, root.left, root.right); - // 递归 - if (root.left != null) Mirror(root.left); - if (root.right != null) Mirror(root.right); - - } - - private void swap(TreeNode root, TreeNode left, TreeNode right) { - TreeNode temp = left; - root.left = right; - root.right = temp; - } -} diff --git a/SwordOffer/src/web/T19.java b/SwordOffer/src/web/T19.java deleted file mode 100644 index dbd13a49..00000000 --- a/SwordOffer/src/web/T19.java +++ /dev/null @@ -1,65 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 顺时针打印矩阵 - * @author: mf - * @create: 2020/01/10 16:22 - */ - -import java.util.ArrayList; - -/** - * 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字, - * 例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - * 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. - */ -public class T19 { - public ArrayList printMatrix(int [][] matrix) { - ArrayList list = new ArrayList<>(); - if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return list; - int start = 0; - int rows = matrix.length; - int cols = matrix[0].length; - while (rows > start * 2 && cols > start * 2) { - print(matrix, rows, cols, start, list); - start++; - } - return list; - } - - /** - * 打印圈 - * @param arr - * @param rows - * @param cols - * @param start - * @param list - */ - private void print(int[][] arr, int rows, int cols, int start, ArrayList list) { - int endX = cols - 1 - start; - int endY = rows - 1 - start; - // 从左到右打印一行 - for(int i = start; i <= endX; i++) { - list.add(arr[start][i]); - } - // 从上到下打印一列 - if(start < endY) { - for(int i = start + 1; i <= endY; i++){ - list.add(arr[i][endX]); - } - } - - // 从右往左打印哟行 - if(start < endX && start < endY) { - for(int i = endX - 1; i >= start; i--) { - list.add(arr[endY][i]); - } - } - - // 从下往上打印一列 - if(start < endX && start < endY) { - for(int i = endY - 1; i >= start + 1; i--) { - list.add(arr[i][start]); - } - } - } -} diff --git a/SwordOffer/src/web/T2.java b/SwordOffer/src/web/T2.java deleted file mode 100644 index 2d3cf80d..00000000 --- a/SwordOffer/src/web/T2.java +++ /dev/null @@ -1,37 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 替换空格 - * @author: mf - * @create: 2020/01/07 20:11 - */ - -/** - * 请实现一个函数,将一个字符串中的每个空格替换成“%20”。 - * 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 - * 思路: - * 1。 检测空格数目 - * 2。 创建新数组 - * 3。 遍历检测' ',则替换相应字符 - */ -public class T2 { - public String replaceSpace(StringBuffer str) { - // 检测空格数目 - int spaceNum = 0; - for (int i = 0; i < str.length(); i++) { - if (str.charAt(i) == ' ') spaceNum++; - } - // 创建新数组 - char[] ans = new char[str.length() + 2 * spaceNum]; - int p1 = ans.length - 1; - for (int i = str.length() - 1; i >= 0; i++) { - if (str.charAt(i) == ' ') { - ans[p1--] = '0'; - ans[p1--] = '2'; - ans[p1--] = '%'; - } else { - ans[p1--] = str.charAt(i); - } - } - return new String(ans); - } -} diff --git a/SwordOffer/src/web/T20.java b/SwordOffer/src/web/T20.java deleted file mode 100644 index 708b50c1..00000000 --- a/SwordOffer/src/web/T20.java +++ /dev/null @@ -1,41 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 包含min函数的栈 - * @author: mf - * @create: 2020/01/12 23:33 - */ - -import java.util.Stack; - -/** - * 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。 - * 用两个栈把 - */ -public class T20 { - - private Stack stack = new Stack<>(); - - private Stack stack2 = new Stack<>(); - - public void push(int node) { - stack.push(node); - if (stack2.isEmpty() || node < stack2.peek()) { - stack2.push(node); - } else { - stack2.push(stack2.peek()); - } - } - - public void pop() { - stack.pop(); - stack2.pop(); - } - - public int top() { - return stack.peek(); - } - - public int min() { - return stack2.peek(); - } -} diff --git a/SwordOffer/src/web/T21.java b/SwordOffer/src/web/T21.java deleted file mode 100644 index 56bf336a..00000000 --- a/SwordOffer/src/web/T21.java +++ /dev/null @@ -1,29 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 栈的压入、弹出序列 - * @author: mf - * @create: 2020/01/12 23:39 - */ - -import java.util.Stack; - -/** - *输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。 - * 假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列, - * 但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) - */ -public class T21 { - public boolean IsPopOrder(int [] pushA,int [] popA) { - if (pushA == null || popA == null) return false; - int p = 0; - Stack stack = new Stack<>(); - for (int i = 0; i < pushA.length; i++) { - stack.push(pushA[i]); - while (!stack.isEmpty() && stack.peek() == popA[p]) { - stack.pop(); - p++; - } - } - return stack.isEmpty(); - } -} diff --git a/SwordOffer/src/web/T22.java b/SwordOffer/src/web/T22.java deleted file mode 100644 index 3b0beeb1..00000000 --- a/SwordOffer/src/web/T22.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 从上往下打印二叉树 - * @author: mf - * @create: 2020/01/13 10:23 - */ - - - -import java.util.ArrayList; -import java.util.LinkedList; - -/** - * 从上往下打印出二叉树的每个节点,同层节点从左至右打印。 - */ -public class T22 { - public ArrayList PrintFromTopToBottom(TreeNode root) { - ArrayList list = new ArrayList<>(); - if (root == null) return list; - // 队列 - LinkedList queue = new LinkedList<>(); - queue.offer(root); - while (!queue.isEmpty()) { - TreeNode node = queue.poll(); - list.add(node.val); - if (node.left != null) queue.offer(node.left); - if (node.right != null) queue.offer(node.right); - } - return list; - } -} diff --git a/SwordOffer/src/web/T23.java b/SwordOffer/src/web/T23.java deleted file mode 100644 index 7e0dd958..00000000 --- a/SwordOffer/src/web/T23.java +++ /dev/null @@ -1,42 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉搜索树的后序遍历序列 - * @author: mf - * @create: 2020/01/13 10:31 - */ - -/** - * 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。 - * 假设输入的数组的任意两个数字都互不相同。 - */ -public class T23 { - public boolean VerifySquenceOfBST(int [] sequence) { - if (sequence == null || sequence.length == 0) return false; - return isBST(sequence, 0, sequence.length - 1); - } - - private boolean isBST(int[] seq, int start, int end) { - if (start >= end) { - return true; - } - int inx = seq[end]; - int m = start; - // 找到分界点 - for (int i = end - 1; i >= start; i--) { - if (seq[i] < inx) { - m = i; - break; - } - if (i == start) { - m = -1; - } - } - // 分界点前的数据都小于根节点 - for (int i = start; i <= m; i++) { - if (seq[i] > inx) return false; - } - - // 递归 - return isBST(seq, start, m) && isBST(seq, m + 1, end - 1); - } -} diff --git a/SwordOffer/src/web/T24.java b/SwordOffer/src/web/T24.java deleted file mode 100644 index f92f9446..00000000 --- a/SwordOffer/src/web/T24.java +++ /dev/null @@ -1,33 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉树中和为某一值的路径 - * @author: mf - * @create: 2020/01/13 10:43 - */ - -import java.util.ArrayList; - -/** - * 输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。 - * 路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 - * (注意: 在返回值的list中,数组长度大的数组靠前) - */ -public class T24 { - - private ArrayList> listall = new ArrayList<>(); - - private ArrayList list = new ArrayList<>(); - - public ArrayList> FindPath(TreeNode root,int target) { - if (root == null) return listall; - list.add(root.val); - target -= root.val; - if (target == 0 && root.left == null && root.right == null) { - listall.add(new ArrayList<>(list)); - } - FindPath(root.left, target); - FindPath(root.right, target); - list.remove(list.size() - 1); - return listall; - } -} diff --git a/SwordOffer/src/web/T25.java b/SwordOffer/src/web/T25.java deleted file mode 100644 index e6cb2933..00000000 --- a/SwordOffer/src/web/T25.java +++ /dev/null @@ -1,38 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 复杂链表的复制 - * @author: mf - * @create: 2020/01/13 13:09 - */ - - -/** - * 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。 - * (注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) - */ -public class T25 { - public RandomListNode Clone(RandomListNode pHead) { - if (pHead == null) return null; - RandomListNode node = pHead; - while (node != null) { - RandomListNode copyNode = new RandomListNode(node.label); - copyNode.next = node.next; - node.next = copyNode; - node = copyNode.next; - } - node = pHead; - while (node != null) { - node.next.random = node.random == null ? null : node.random.next; - node = node.next.next; - } - node = pHead; - RandomListNode pCloneHead = pHead.next; - while (node != null) { - RandomListNode copyNode = node.next; - node.next = copyNode.next; - copyNode.next = copyNode.next == null ? null : copyNode.next.next; - node = node.next; - } - return pCloneHead; - } -} diff --git a/SwordOffer/src/web/T26.java b/SwordOffer/src/web/T26.java deleted file mode 100644 index e6de9c82..00000000 --- a/SwordOffer/src/web/T26.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉搜索树与双向链表 - * @author: mf - * @create: 2020/01/13 13:42 - */ - -/** - * 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 - */ -public class T26 { - public TreeNode Convert(TreeNode pRootOfTree) { - if (pRootOfTree == null) return null; - if (pRootOfTree.left == null && pRootOfTree.right == null) return pRootOfTree; - TreeNode left = Convert(pRootOfTree.left); - TreeNode p = left; - while (p != null && p.right != null) { - p = p.right; - } - if (left != null) { - p.right = pRootOfTree; - pRootOfTree.left = p; - } - TreeNode right = Convert(pRootOfTree.right); - if (right != null) { - pRootOfTree.right = right; - right.left = pRootOfTree; - } - return left != null ? left : pRootOfTree; - } -} diff --git a/SwordOffer/src/web/T27.java b/SwordOffer/src/web/T27.java deleted file mode 100644 index 84795427..00000000 --- a/SwordOffer/src/web/T27.java +++ /dev/null @@ -1,55 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 字符串的排列 - * @author: mf - * @create: 2020/01/13 21:52 - */ - - -import java.util.ArrayList; -import java.util.TreeSet; - -/** - * 输入一个字符串,按字典序打印出该字符串中字符的所有排列。 - * 例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba - */ -public class T27 { - public ArrayList Permutation(String str) { - ArrayList result = new ArrayList<>(); - if(str == null || str.length() == 0) { - return result; - } - char[] chars = str.toCharArray(); - TreeSet temp = new TreeSet<>(); - Permutation(chars, 0, temp); - result.addAll(temp); - return result; - } - - private void Permutation(char[] chars, int begin, TreeSet result) { - if (chars == null || chars.length == 0 || begin < 0 || begin > chars.length - 1) { - return; - } - if (begin == chars.length - 1) { - result.add(String.valueOf(chars)); - } else { - for (int i = begin; i <= chars.length - 1; i++) { - swap(chars, begin, i); - Permutation(chars, begin + 1, result); - swap(chars, begin, i); - } - } - } - - /** - * 交换 - * @param x - * @param a - * @param b - */ - private void swap(char[] x, int a, int b) { - char t = x[a]; - x[a] = x[b]; - x[b] = t; - } -} diff --git a/SwordOffer/src/web/T28.java b/SwordOffer/src/web/T28.java deleted file mode 100644 index a65f1122..00000000 --- a/SwordOffer/src/web/T28.java +++ /dev/null @@ -1,35 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数组中出现次数超过一半的数字 - * @author: mf - * @create: 2020/01/14 00:19 - */ - -import java.util.HashMap; -import java.util.Map; - -/** - * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 - * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。 - * 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。 - */ -public class T28 { - public int MoreThanHalfNum_Solution(int [] array) { - // 哈希 - HashMap map = new HashMap<>(); - for (int i = 0; i < array.length; i++) { - if (map.containsKey(array[i])) { - map.put(array[i], map.get(array[i]) + 1); - } else { - map.put(array[i], 1); - } - } - int length = array.length >> 1; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() > length) { - return entry.getKey(); - } - } - return 0; - } -} diff --git a/SwordOffer/src/web/T29.java b/SwordOffer/src/web/T29.java deleted file mode 100644 index f31703a4..00000000 --- a/SwordOffer/src/web/T29.java +++ /dev/null @@ -1,45 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 最小的k个数 - * @author: mf - * @create: 2020/01/14 00:22 - */ - -import java.util.ArrayList; - -/** - * 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 - */ -public class T29 { - public ArrayList GetLeastNumbers_Solution(int [] input, int k) { - ArrayList integers = new ArrayList<>(); - if (k > input.length){ - return integers; - } - // 先排序。。。 - for (int i = 1; i < input.length; i++) { - for (int j = 0; j < input.length - i; j++) { - if (input[j] > input[j + 1]) { - swap(input, j, j + 1); - } - } - } - // - for (int i = 0; i < k; i++) { - integers.add(input[i]); - } - return integers; - } - - /** - * 交换 - * @param arr - * @param i - * @param j - */ - private void swap(int[] arr, int i, int j) { - int temp = arr[i]; - arr[i] = arr[j]; - arr[j] = temp; - } -} diff --git a/SwordOffer/src/web/T3.java b/SwordOffer/src/web/T3.java deleted file mode 100644 index 9c62d053..00000000 --- a/SwordOffer/src/web/T3.java +++ /dev/null @@ -1,27 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 从尾到头打印链表 - * @author: mf - * @create: 2020/01/08 16:16 - */ - -import java.util.ArrayList; - -/** - * 输入一个链表,按链表从尾到头的顺序返回一个ArrayList。 - * 思路: - * - */ -public class T3 { - // 创建list - ArrayList list = new ArrayList<>(); - public ArrayList printListFromTailToHead(ListNode listNode) { - // 判断头节点是否为空 - if (listNode != null) { - // 递归 - this.printListFromTailToHead(listNode.next); - list.add(listNode.val); - } - return list; - } -} diff --git a/SwordOffer/src/web/T30.java b/SwordOffer/src/web/T30.java deleted file mode 100644 index 306c2b07..00000000 --- a/SwordOffer/src/web/T30.java +++ /dev/null @@ -1,27 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 连续子数组的最大和 - * @author: mf - * @create: 2020/01/14 00:25 - */ - -/** - * HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。 - * 今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。 - * 但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢? - * 例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。 - * 给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1) - */ -public class T30 { - public int FindGreatestSumOfSubArray(int[] array) { - // 动态规划完事 - if (array == null || array.length == 0) return 0; - int res = array[0]; // 记录当前所有子数组的和的最大值 - int max = array[0]; // 记录包含arr[i]的连续子数组的最大值 - for (int i = 1; i < array.length; i++) { - max = Math.max(max + array[i], array[i]); - res = Math.max(max, res); - } - return res; - } -} diff --git a/SwordOffer/src/web/T31.java b/SwordOffer/src/web/T31.java deleted file mode 100644 index d7982c96..00000000 --- a/SwordOffer/src/web/T31.java +++ /dev/null @@ -1,27 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 整数中1出现的次数 - * @author: mf - * @create: 2020/01/14 00:27 - */ - -/** - * 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数? - * 为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。 - * ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。 - */ -public class T31 { - public int NumberOf1Between1AndN_Solution(int n) { - int count = 0; - for (int i = 1; i <= n; i++) { - int num = i; - while(num != 0) { - if (num % 10 == 1) { - count++; - } - num /= 10; - } - } - return count; - } -} diff --git a/SwordOffer/src/web/T32.java b/SwordOffer/src/web/T32.java deleted file mode 100644 index 1bbe120e..00000000 --- a/SwordOffer/src/web/T32.java +++ /dev/null @@ -1,37 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 把数组排成最小的数 - * @author: mf - * @create: 2020/01/14 00:33 - */ - -import java.util.Arrays; -import java.util.Comparator; - -/** - * 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 - * 例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 - */ -public class T32 { - public String PrintMinNumber(int [] numbers) { - if (numbers == null || numbers.length == 0) return ""; - int len = numbers.length; - String[] str = new String[len]; - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < len; i++) { - str[i] = String.valueOf(numbers[i]); - } - Arrays.sort(str, new Comparator() { - @Override - public int compare(String o1, String o2) { - String c1 = o1 + o2; - String c2 = o2 + o1; - return c1.compareTo(c2); - } - }); - for (int i = 0; i < len; i++) { - sb.append(str[i]); - } - return sb.toString(); - } -} diff --git a/SwordOffer/src/web/T33.java b/SwordOffer/src/web/T33.java deleted file mode 100644 index 2030563f..00000000 --- a/SwordOffer/src/web/T33.java +++ /dev/null @@ -1,40 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 丑数 - * @author: mf - * @create: 2020/01/14 14:03 - */ - - -/** - * 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 - * 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 - */ -public class T33 { - public int GetUglyNumber_Solution(int index) { - if (index <= 0) return 0; - int[] ans = new int[index]; - int count = 0; - int i2 = 0, i3 = 0, i5 = 0; - ans[0] = 1; - int temp = 0; - while (count < index - 1) { - temp = min(ans[i2] * 2, min(ans[i3] * 3, ans[i5] * 5)); - if (temp == ans[i2] * 2) i2++; - if (temp == ans[i3] * 3) i3++; - if (temp == ans[i5] * 5) i5++; - ans[++count] = temp; - } - return ans[index - 1]; - } - - /** - * 求最小值 - * @param a - * @param b - * @return - */ - private int min(int a, int b) { - return (a > b) ? b : a; - } -} diff --git a/SwordOffer/src/web/T34.java b/SwordOffer/src/web/T34.java deleted file mode 100644 index a810f6e1..00000000 --- a/SwordOffer/src/web/T34.java +++ /dev/null @@ -1,34 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 第一个只出现一次的字符 - * @author: mf - * @create: 2020/01/14 17:20 - */ - - -import java.util.HashMap; - -/** - * 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写). - */ -public class T34 { - - public int FirstNotRepeatingChar(String str) { - if (str == null || str.length() == 0) return -1; - HashMap map = new HashMap<>(); - for (int i = 0; i < str.length(); i++) { - if (map.containsKey(str.charAt(i))) { - map.put(str.charAt(i), map.get(str.charAt(i) + 1)); - } else { - map.put(str.charAt(i), 1); - } - } - - for (int i = 0; i < str.length(); i++) { - if (map.get(str.charAt(i)) == 1) { - return i; - } - } - return -1; - } -} diff --git a/SwordOffer/src/web/T35.java b/SwordOffer/src/web/T35.java deleted file mode 100644 index cebec1db..00000000 --- a/SwordOffer/src/web/T35.java +++ /dev/null @@ -1,58 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数组中的逆序对 - * @author: mf - * @create: 2020/01/15 09:45 - */ - -/** - * 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 - * 输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007 - */ -public class T35 { - - private Integer count = 0; - - public int InversePairs(int [] array) { - if (array == null || array.length == 0) return 0; - mergeSort(array, 0, array.length - 1); - return (count % 1000000007); - } - - private void mergeSort(int[] array, int left, int right) { - if (left < right) { - int mid = (left + right) >> 1; - mergeSort(array, left ,mid); - mergeSort(array, mid + 1, right); - merge(array, left, mid, right); - } - - } - - private void merge(int[] array, int left, int mid, int right) { - int[] help = new int[right - left + 1]; - int i = 0; - int p1 = left; - int p2 = mid + 1; - while(p1 <= mid && p2 <= right) { - if(array[p1] > array[p2]) { - help[i++] = array[p2++]; - count += mid - p1 + 1; - } else { - help[i++] = array[p1++]; - } - } - - while(p1 <= mid) { - help[i++] = array[p1++]; - } - - while(p2 <= right) { - help[i++] = array[p2++]; - } - - for(int j = 0; j < help.length; j++) { - array[left + j] = help[j]; - } - } -} diff --git a/SwordOffer/src/web/T36.java b/SwordOffer/src/web/T36.java deleted file mode 100644 index 13b9f1cf..00000000 --- a/SwordOffer/src/web/T36.java +++ /dev/null @@ -1,32 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 两个链表的第一个公共结点 - * @author: mf - * @create: 2020/01/15 09:48 - */ - -import java.util.HashMap; - -/** - * 输入两个链表,找出它们的第一个公共结点。 - */ -public class T36 { - - public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - if (pHead1 == null || pHead2 == null) return null; - ListNode cur1 = pHead1; - ListNode cur2 = pHead2; - HashMap map = new HashMap<>(); - while (cur1 != null) { - map.put(cur1, 1); - cur1 = cur1.next; - } - while (cur2 != null) { - if (map.containsKey(cur2)) { - return cur2; - } - cur2 = cur2.next; - } - return null; - } -} diff --git a/SwordOffer/src/web/T37.java b/SwordOffer/src/web/T37.java deleted file mode 100644 index 7083a5ea..00000000 --- a/SwordOffer/src/web/T37.java +++ /dev/null @@ -1,19 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数字在排序数组中出现的次数 - * @author: mf - * @create: 2020/01/15 09:57 - */ - -/** - * 统计一个数字在排序数组中出现的次数。 - */ -public class T37 { - public int GetNumberOfK(int [] array , int k) { - int count = 0; - for (int i : array) { - if (i == k) count++; - } - return count; - } -} diff --git a/SwordOffer/src/web/T38.java b/SwordOffer/src/web/T38.java deleted file mode 100644 index c8cbab80..00000000 --- a/SwordOffer/src/web/T38.java +++ /dev/null @@ -1,18 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉树的深度 - * @author: mf - * @create: 2020/01/15 10:17 - */ - -/** - * 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 - */ -public class T38 { - public int TreeDepth(TreeNode root) { - if (root == null) return 0; - int left = TreeDepth(root.left); - int right = TreeDepth(root.right); - return left > right ? (left + 1) : (right + 1); - } -} diff --git a/SwordOffer/src/web/T39.java b/SwordOffer/src/web/T39.java deleted file mode 100644 index 0d2649c6..00000000 --- a/SwordOffer/src/web/T39.java +++ /dev/null @@ -1,24 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 平衡二叉树 - * @author: mf - * @create: 2020/01/15 10:20 - */ - - -/** - * 输入一棵二叉树,判断该二叉树是否是平衡二叉树。 - */ -public class T39 { - public boolean IsBalanced_Solution(TreeNode root) { - if (root == null) { - return true; - } - return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 && IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right); - } - - private int maxDepth(TreeNode root) { - if(root == null) return 0; - return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); - } -} diff --git a/SwordOffer/src/web/T4.java b/SwordOffer/src/web/T4.java deleted file mode 100644 index cfc7ec32..00000000 --- a/SwordOffer/src/web/T4.java +++ /dev/null @@ -1,34 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 重建二叉树 - * @author: mf - * @create: 2020/01/08 17:01 - */ - -/** - * 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。 - * 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 - * 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}, - * 则重建二叉树并返回。 - * - *关键在于左节点和右节点的范围 - */ -public class T4 { - - public TreeNode reConstructBinaryTree(int [] pre,int [] in) { - TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); - return root; - } - - private TreeNode reConstructBinaryTree(int[] pre, int startPre, int endPre, int[] in, int startIn, int endIn) { - if(startPre > endPre || startIn > endIn) return null; - TreeNode root = new TreeNode(pre[startPre]); - for(int i = startIn; i <= endPre; i++) { - if(in[i] == pre[startPre]) { - root.left = reConstructBinaryTree(pre, startPre + 1, startPre + i - startIn, in, startIn, i - 1); - root.right = reConstructBinaryTree(pre, startPre + i + 1 - startIn, endPre, in, i + 1, endIn); - } - } - return root; - } -} diff --git a/SwordOffer/src/web/T40.java b/SwordOffer/src/web/T40.java deleted file mode 100644 index 852867dc..00000000 --- a/SwordOffer/src/web/T40.java +++ /dev/null @@ -1,39 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数组中只出现一次的数字 - * @author: mf - * @create: 2020/01/15 10:27 - */ - -import java.util.HashMap; -import java.util.Map; - -/** - * 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。 - */ -public class T40 { - public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { - if (array == null || array.length <= 1) { - num1[0] = num2[0] = 0; - return; - } - HashMap map = new HashMap<>(); - for (int i = 0; i < array.length; i++) { - if (map.containsKey(array[i])) { - map.put(array[i], 2); - } else { - map.put(array[i], 1); - } - } - StringBuffer sb = new StringBuffer(); - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 1) { - sb.append(entry.getKey()); - sb.append(","); - } - } - String[] strings = sb.toString().split(","); - num1[0] = Integer.valueOf(strings[0]); - num2[0] = Integer.valueOf(strings[1]); - } -} diff --git a/SwordOffer/src/web/T41.java b/SwordOffer/src/web/T41.java deleted file mode 100644 index d8fca21f..00000000 --- a/SwordOffer/src/web/T41.java +++ /dev/null @@ -1,40 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 和为S的连续正数序列 - * @author: mf - * @create: 2020/01/15 10:37 - */ - -import java.util.ArrayList; - -/** - * 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。 - * 但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。 - * 没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。 - * 现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck! - */ -public class T41 { - public ArrayList > FindContinuousSequence(int sum) { - ArrayList> arrayLists = new ArrayList<>(); - int phigh = 2; - int plow = 1; - while(phigh > plow) { - int cur = (phigh + plow) * (phigh - plow + 1) / 2; - if (cur < sum) { - phigh++; - } - if (cur == sum) { - ArrayList arrayList = new ArrayList<>(); - for (int i = plow; i <= phigh; i++) { - arrayList.add(i); - } - arrayLists.add(arrayList); - plow++; - } - if (cur > sum) { - plow++; - } - } - return arrayLists; - } -} diff --git a/SwordOffer/src/web/T42.java b/SwordOffer/src/web/T42.java deleted file mode 100644 index 672ca335..00000000 --- a/SwordOffer/src/web/T42.java +++ /dev/null @@ -1,34 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 和为S的两个数字 - * @author: mf - * @create: 2020/01/15 10:48 - */ - -import java.util.ArrayList; - -/** - * 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。 - */ -public class T42 { - - public ArrayList FindNumbersWithSum(int [] array, int sum) { - int start = 0, end = array.length - 1; - ArrayList list = new ArrayList<>(); - while (start < end) { - int count = array[start] + array[end]; - if (count < sum) { - start++; - } - if (count == sum) { - list.add(array[start]); - list.add(array[end]); - return list; - } - if (count > sum) { - end--; - } - } - return list; - } -} diff --git a/SwordOffer/src/web/T43.java b/SwordOffer/src/web/T43.java deleted file mode 100644 index ffda5629..00000000 --- a/SwordOffer/src/web/T43.java +++ /dev/null @@ -1,22 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 左旋转字符串 - * @author: mf - * @create: 2020/01/15 13:12 - */ - -/** - * 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。 - * 对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。 - * 例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它! - */ -public class T43 { - public String LeftRotateString(String str,int n) { - if (str.length() == 0) return str; - for (int i = 0; i < n; i++) { - char c = str.charAt(0); - str = str.substring(1).concat(String.valueOf(c)); - } - return str; - } -} diff --git a/SwordOffer/src/web/T44.java b/SwordOffer/src/web/T44.java deleted file mode 100644 index 333d889a..00000000 --- a/SwordOffer/src/web/T44.java +++ /dev/null @@ -1,26 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 翻转单词顺序列 - * @author: mf - * @create: 2020/01/15 13:15 - */ - -/** - * 牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。 - * 同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。 - * 例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。 - * Cat对一一的翻转这些单词顺序可不在行,你能帮助他么? - */ -public class T44 { - - public String ReverseSentence(String str) { - if (str == null) return null; - if (str.trim().equals("")) return str; - String[] strings = str.split(" "); - StringBuffer sb = new StringBuffer(); - for (int i = strings.length - 1; i >= 0; i++) { - sb.append(strings[i]).append(" "); - } - return sb.substring(0, sb.length()); - } -} diff --git a/SwordOffer/src/web/T45.java b/SwordOffer/src/web/T45.java deleted file mode 100644 index dd45bf5a..00000000 --- a/SwordOffer/src/web/T45.java +++ /dev/null @@ -1,38 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 扑克牌顺序 - * @author: mf - * @create: 2020/01/15 13:19 - */ - -import java.util.Arrays; - -/** - * LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)... - * 他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!! - * “红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。 - * 上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 - * 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。 - * 为了方便起见,你可以认为大小王是0。 - */ -public class T45 { - public boolean isContinuous(int [] numbers) { - int numOfZero = 0; - int numOfInterval = 0; - int length = numbers.length; - if (length == 0) return false; - Arrays.sort(numbers); - for (int i = 0; i < length - 1; i++) { - // 计算癞子数量 - if (numbers[i] == 0) { - numOfZero++; - continue; - } - // 对子直接返回 - if (numbers[i] == numbers[i + 1]) return false; - numOfInterval += numbers[i + 1] - numbers[i] - 1; - } - if (numOfZero >= numOfInterval) return true; - return false; - } -} diff --git a/SwordOffer/src/web/T46.java b/SwordOffer/src/web/T46.java deleted file mode 100644 index b17d3c36..00000000 --- a/SwordOffer/src/web/T46.java +++ /dev/null @@ -1,37 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 孩子们的游戏 - * @author: mf - * @create: 2020/01/15 13:25 - */ - -import java.util.ArrayList; - -/** - * 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。 - * HF作为牛客的资深元老,自然也准备了一些小游戏。 - * 其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。 - * 然后,他随机指定一个数m,让编号为0的小朋友开始报数。 - * 每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始, - * 继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。 - * 请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1) - * 如果没有小朋友,请返回-1 - */ -public class T46 { - public int LastRemaining_Solution(int n, int m) { - if (n == 0 || m == 0) { - return -1; - } - ArrayList data = new ArrayList<>(); - for (int i = 0; i < n; i++) { - data.add(i); - } - int index = -1; - while (data.size() > 1) { - index = (index + m) % data.size(); - data.remove(index); - index--; - } - return data.get(0); - } -} diff --git a/SwordOffer/src/web/T47.java b/SwordOffer/src/web/T47.java deleted file mode 100644 index be769423..00000000 --- a/SwordOffer/src/web/T47.java +++ /dev/null @@ -1,17 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 求1+2+3+...+n - * @author: mf - * @create: 2020/01/15 13:42 - */ - -/** - * 求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 - */ -public class T47 { - public int Sum_Solution(int n) { - int res = n; - boolean t = ((res != 0) && ((res += Sum_Solution(n - 1)) != 0)); - return res; - } -} diff --git a/SwordOffer/src/web/T48.java b/SwordOffer/src/web/T48.java deleted file mode 100644 index a984d8ad..00000000 --- a/SwordOffer/src/web/T48.java +++ /dev/null @@ -1,20 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 不用加减乘除做加法 - * @author: mf - * @create: 2020/01/15 13:47 - */ - -/** - *写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。 - */ -public class T48 { - public int Add(int num1,int num2) { - while (num2 != 0) { - int temp = num1 ^ num2; - num2 = (num1 & num2) << 1; - num1 = temp; - } - return num1; - } -} diff --git a/SwordOffer/src/web/T49.java b/SwordOffer/src/web/T49.java deleted file mode 100644 index 26b41ce6..00000000 --- a/SwordOffer/src/web/T49.java +++ /dev/null @@ -1,37 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 把字符串转成整数 - * @author: mf - * @create: 2020/01/16 13:55 - */ - -/** - * 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 - * 数值为0或者字符串不是一个合法的数值则返回0 - */ -public class T49 { - public int StrToInt(String str) { - if (str.trim().equals("") || str.length() == 0) { - return 0; - } - if (str.equals("-2147483649") || str.equals("2147483648")) { - return 0; - } - char[] chars = str.toCharArray(); - int sign = 0; - if (chars[0] == '-') { - sign = 1; - } - int sum = 0; - for (int i = sign; i < chars.length; i++) { - if (chars[i] == '+') { - continue; - } - if (chars[i] < 48 || chars[i] > 57) { - return 0; - } - sum = sum * 10 + chars[i] - 48; - } - return sign == 0 ? sum : sum * -1; - } -} diff --git a/SwordOffer/src/web/T5.java b/SwordOffer/src/web/T5.java deleted file mode 100644 index b5f93b8d..00000000 --- a/SwordOffer/src/web/T5.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 用两个栈实现队列 - * @author: mf - * @create: 2020/01/09 12:01 - */ - -import java.util.Stack; - -/** - * 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。 - */ -public class T5 { - - Stack stack1 = new Stack<>(); - - Stack stack2 = new Stack<>(); - - public void push (int node) { - stack1.push(node); - } - - public int pop() { - if (stack2.isEmpty()) { - while (!stack1.isEmpty()) { - stack2.push(stack1.pop()); - } - } - return stack2.pop(); - } -} diff --git a/SwordOffer/src/web/T50.java b/SwordOffer/src/web/T50.java deleted file mode 100644 index 164f6b92..00000000 --- a/SwordOffer/src/web/T50.java +++ /dev/null @@ -1,32 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数组中重复的数字 - * @author: mf - * @create: 2020/01/16 13:58 - */ - -import java.util.Arrays; - -/** - * 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 - * 数组中某些数字是重复的,但不知道有几个数字是重复的。 - * 也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 - * 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 - */ -public class T50 { - public boolean duplicate(int numbers[],int length,int [] duplication) { - if (numbers == null || numbers.length == 0) { - return false; - } - Arrays.sort(numbers); - int flag = 0; - for (int i = 0; i < length - 1; i++) { - if (numbers[i] == numbers[i + 1]) { - duplication[0] = numbers[i]; - flag = 1; - break; - } - } - return flag == 1 ? true : false; - } -} diff --git a/SwordOffer/src/web/T51.java b/SwordOffer/src/web/T51.java deleted file mode 100644 index fd6e01cd..00000000 --- a/SwordOffer/src/web/T51.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 构建乘积数组 - * @author: mf - * @create: 2020/01/16 14:01 - */ - -/** - * 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。 - * 不能使用除法。 - */ -public class T51 { - public int[] multiply(int[] A) { - int length = A.length; - int[] B = new int[length]; - if (length != 0) { - B[0] = 1; - // 计算下三角连乘 - for (int i = 1; i < length; i++) { - B[i] = B[i - 1] * A[i - 1]; - } - int temp = 1; - // 计算上三角 - for (int j = length - 2; j >= 0; j--) { - temp *= A[j + 1]; - B[j] *= temp; - } - } - return B; - } -} diff --git a/SwordOffer/src/web/T52.java b/SwordOffer/src/web/T52.java deleted file mode 100644 index c272e1da..00000000 --- a/SwordOffer/src/web/T52.java +++ /dev/null @@ -1,46 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 正则表达式匹配 - * @author: mf - * @create: 2020/01/16 14:03 - */ - -/** - * 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。 - * 例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配 - */ -public class T52 { - public boolean match(char[] str, char[] pattern) { - if (str == null || pattern == null) { - return false; - } - int strIndex = 0; - int patternIndex = 0; - return matchCore(str, strIndex, pattern, patternIndex); - } - - public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) { - // 有效性校验:str到尾, pattern到尾,匹配成功 - if (strIndex == str.length && patternIndex == pattern.length) { - return true; - } - // pattern 先到尾,匹配失败 - if (strIndex != str.length && patternIndex == pattern.length) { - return false; - } - // 模式第二个是*,且字符串第一个根模式第一个匹配,分三种匹配模式; - // 如果不匹配,模式后移两 - if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') { - if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) { - return matchCore(str, strIndex, pattern, patternIndex + 2) || matchCore(str, strIndex + 1, pattern, patternIndex + 2) || matchCore(str, strIndex + 1, pattern, patternIndex); - } else { - return matchCore(str, strIndex, pattern, patternIndex + 2); - } - } - // 模式第二个不是*,且字符串第一个根模式第一个匹配,则都后移一位,否则直接返回false - if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])|| (pattern[patternIndex] == '.' && strIndex != str.length)) { - return matchCore(str, strIndex + 1, pattern, patternIndex + 1); - } - return false; - } -} diff --git a/SwordOffer/src/web/T53.java b/SwordOffer/src/web/T53.java deleted file mode 100644 index 128002dd..00000000 --- a/SwordOffer/src/web/T53.java +++ /dev/null @@ -1,17 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 表示数值的字符串 - * @author: mf - * @create: 2020/01/16 14:05 - */ - -/** - * 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 - * 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。 - */ -public class T53 { - public boolean isNumeric(char[] str) { - String s = String.valueOf(str); - return s.matches("[+-]?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]+)?"); - } -} diff --git a/SwordOffer/src/web/T54.java b/SwordOffer/src/web/T54.java deleted file mode 100644 index e091ec03..00000000 --- a/SwordOffer/src/web/T54.java +++ /dev/null @@ -1,39 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 字符流中第一个不重复的字符 - * @author: mf - * @create: 2020/01/16 14:10 - */ - -/** - * 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。 - * 当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。 - */ -public class T54 { - - int count[] = new int[256]; - - int index = 1; - - public void Insert(char ch) - { - if (count[ch] == 0) { - count[ch] = index++; - } else { - count[ch] = -1; - } - } - - public char FirstAppearingOnce() - { - int temp = Integer.MAX_VALUE; - char ch = '#'; - for (int i = 0; i < count.length; i++) { - if (count[i] != 0 && count[i] != -1 && count[i] < temp) { - temp = count[i]; - ch = (char)i; - } - } - return ch; - } -} diff --git a/SwordOffer/src/web/T55.java b/SwordOffer/src/web/T55.java deleted file mode 100644 index 70c19b2c..00000000 --- a/SwordOffer/src/web/T55.java +++ /dev/null @@ -1,29 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 链表中环的入口结点 - * @author: mf - * @create: 2020/01/16 14:13 - */ - -import java.util.HashMap; - -/** - * 给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。 - */ -public class T55 { - public ListNode EntryNodeOfLoop(ListNode pHead) { - if (null == pHead) { - return null; - } - HashMap map = new HashMap<>(); - map.put(pHead, 1); - while (null != pHead.next) { - if (map.containsKey(pHead.next)) { - return pHead.next; - } - map.put(pHead.next, 1); - pHead = pHead.next; - } - return null; - } -} diff --git a/SwordOffer/src/web/T56.java b/SwordOffer/src/web/T56.java deleted file mode 100644 index 4444962b..00000000 --- a/SwordOffer/src/web/T56.java +++ /dev/null @@ -1,33 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 删除链表中重复的结点 - * @author: mf - * @create: 2020/01/16 14:18 - */ - -/** - * - 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 - 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 - */ -public class T56 { - public ListNode deleteDuplication(ListNode pHead) { - // 只有0个或1个节点,则返回。 - if (null == pHead || pHead.next == null) { - return pHead; - } - // 当前节点是重复节点 - if (pHead.val == pHead.next.val) { - ListNode pNode = pHead.next; - while (pNode != null && pHead.val == pNode.val) { - pNode = pNode.next; - } - return deleteDuplication(pNode); - } else { - // 当前节点不是重复节点 - // 保留当前节点,从下一个节点开始递归 - pHead.next = deleteDuplication(pHead.next); - return pHead; - } - } -} diff --git a/SwordOffer/src/web/T57.java b/SwordOffer/src/web/T57.java deleted file mode 100644 index 542f1234..00000000 --- a/SwordOffer/src/web/T57.java +++ /dev/null @@ -1,32 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉树的下一个结点 - * @author: mf - * @create: 2020/01/16 14:22 - */ - -/** - * 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。 - * 注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 - */ -public class T57 { - public TreeLinkNode GetNext(TreeLinkNode pNode) { - if (null == pNode) { - return null; - } - if (null != pNode.right) { - pNode = pNode.right; - while (null != pNode.left) { - pNode = pNode.left; - } - return pNode; - } - while (null != pNode.next) { - if (pNode.next.left == pNode) { - return pNode.next; - } - pNode = pNode.next; - } - return null; - } -} diff --git a/SwordOffer/src/web/T58.java b/SwordOffer/src/web/T58.java deleted file mode 100644 index f5a2a8aa..00000000 --- a/SwordOffer/src/web/T58.java +++ /dev/null @@ -1,34 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 对称的二叉树 - * @author: mf - * @create: 2020/01/16 14:26 - */ - -/** - * 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。 - */ -public class T58 { - boolean isSymmetrical(TreeNode pRoot) { - if (null == pRoot) { - return true; - } - return comRoot(pRoot.left, pRoot.right); - } - - private boolean comRoot(TreeNode left, TreeNode right) { - if (left == null) { - return right == null; - } - - if (right == null) { - return false; - } - - if (left.val != right.val) { - return false; - } - - return comRoot(left.right, right.left) && comRoot(left.left, right.right); - } -} diff --git a/SwordOffer/src/web/T59.java b/SwordOffer/src/web/T59.java deleted file mode 100644 index 6e59e875..00000000 --- a/SwordOffer/src/web/T59.java +++ /dev/null @@ -1,53 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 按之字形顺序打印二叉树 - * @author: mf - * @create: 2020/01/16 15:31 - */ - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; - -/** - * 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 - */ -public class T59 { - public ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - if (null == pRoot) return ret; - ArrayList list = new ArrayList<>(); - LinkedList queue = new LinkedList<>(); - queue.addLast(null); // 层分隔符 - queue.addLast(pRoot); - boolean leftToRight = true; - - while(queue.size() != 1) { - TreeNode node = queue.removeFirst(); - if (null == node) { - Iterator iter = null; - if (leftToRight) { - iter = queue.iterator(); - } else { - iter = queue.descendingIterator(); - } - leftToRight = ! leftToRight; - while(iter.hasNext()) { - TreeNode temp = (TreeNode) iter.next(); - list.add(temp.val); - } - ret.add(new ArrayList<>(list)); - list.clear(); - queue.addLast(null); - continue; - } - if (node.left != null) { - queue.addLast(node.left); - } - if (node.right != null) { - queue.addLast(node.right); - } - } - return ret; - } -} diff --git a/SwordOffer/src/web/T6.java b/SwordOffer/src/web/T6.java deleted file mode 100644 index dd571467..00000000 --- a/SwordOffer/src/web/T6.java +++ /dev/null @@ -1,31 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 旋转数组的最小数组 - * @author: mf - * @create: 2020/01/09 12:06 - */ - -/** - * 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 - * 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 - * 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 - * NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 - */ -public class T6 { - public int minNumberInRotateArray(int [] array) { - // 判断条件 - if (array.length == 0) return 0; - if (array.length == 1) return array[0]; - - int a = array[0]; - // 根据数组的特征 - for (int i = 1; i < array.length; i++) { - if (a > array[i]) { - return array[i]; - } else { - a = array[i]; - } - } - return 0; - } -} diff --git a/SwordOffer/src/web/T60.java b/SwordOffer/src/web/T60.java deleted file mode 100644 index 32304b51..00000000 --- a/SwordOffer/src/web/T60.java +++ /dev/null @@ -1,33 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 把二叉树打印多行 - * @author: mf - * @create: 2020/01/17 21:11 - */ - -import java.util.ArrayList; - -/** - * 从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。 - */ -public class T60 { - - ArrayList> Print(TreeNode pRoot) { - ArrayList> list = new ArrayList<>(); - depth(pRoot, 1, list); - return list; - } - - private void depth(TreeNode root, int depth, ArrayList> list) { - if (null == root) { - return; - } - if (depth > list.size()) { - list.add(new ArrayList<>()); - } - - list.get(depth - 1).add(root.val); - depth(root.left, depth + 1, list); - depth(root.right, depth + 1, list); - } -} diff --git a/SwordOffer/src/web/T61.java b/SwordOffer/src/web/T61.java deleted file mode 100644 index d5742067..00000000 --- a/SwordOffer/src/web/T61.java +++ /dev/null @@ -1,54 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 序列化二叉树 - * @author: mf - * @create: 2020/01/17 21:13 - */ - -/** - * 请实现两个函数,分别用来序列化和反序列化二叉树 - * 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。 - * 序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。 - * 二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。 - */ -public class T61 { - String Serialize(TreeNode root) { - if (null == root) { - return ""; - } - StringBuffer sb = new StringBuffer(); - Serialize2(root, sb); - return sb.toString(); - } - - void Serialize2(TreeNode root, StringBuffer sb) { - if (null == root) { - sb.append("#,"); - return; - } - sb.append(root.val); - sb.append(","); - Serialize2(root.left, sb); - Serialize2(root.right, sb); - } - int index = -1; - - TreeNode Deserialize(String str) { - if (str.length() == 0) { - return null; - } - String[] strings = str.split(","); - return Deserialize2(strings); - } - TreeNode Deserialize2(String[] strings) { - index++; - if (!strings[index].equals("#")) { - TreeNode root = new TreeNode(0); - root.val = Integer.parseInt(strings[index]); - root.left = Deserialize2(strings); - root.right = Deserialize2(strings); - return root; - } - return null; - } -} diff --git a/SwordOffer/src/web/T62.java b/SwordOffer/src/web/T62.java deleted file mode 100644 index b1974bb8..00000000 --- a/SwordOffer/src/web/T62.java +++ /dev/null @@ -1,30 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 二叉搜索树的第k个结点 - * @author: mf - * @create: 2020/01/17 23:01 - */ - -/** - * 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8中,按结点数值大小顺序第三小结点的值为4。 - */ -public class T62 { - int index = 0; - TreeNode KthNode(TreeNode pRoot, int k) { - if (null != pRoot) { - TreeNode node = KthNode(pRoot.left, k); - if (null != node) { - return node; - } - index++; - if (index == k) { - return pRoot; - } - node = KthNode(pRoot.right, k); - if (null != node) { - return node; - } - } - return null; - } -} diff --git a/SwordOffer/src/web/T63.java b/SwordOffer/src/web/T63.java deleted file mode 100644 index 9c1a3bea..00000000 --- a/SwordOffer/src/web/T63.java +++ /dev/null @@ -1,49 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 数据流中的中位数 - * @author: mf - * @create: 2020/01/17 23:05 - */ - -import java.util.LinkedList; - -/** - * 如何得到一个数据流中的中位数? - * 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。 - * 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 - * 我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。 - */ -public class T63 { - LinkedList list = new LinkedList<>(); - public void Insert(Integer num) { - if (list.size() == 0 || num < list.getFirst()) { - list.addFirst(num); - } else { - boolean insertFlag = false; - for(Integer e : list) { - if (num < e) { - int index = list.indexOf(e); - list.add(index, num); - insertFlag = true; - break; - } - } - if (!insertFlag) { - list.addLast(num); - } - } - } - - public Double GetMedian() { - if (list.size() == 0) { - return null; - } - if(list.size() % 2 == 0) { - int i = list.size() / 2; - Double a = Double.valueOf(list.get(i - 1) + list.get(i)); - return a / 2; - } - list.get(0); - return Double.valueOf(list.get(list.size() / 2)); - } -} diff --git a/SwordOffer/src/web/T64.java b/SwordOffer/src/web/T64.java deleted file mode 100644 index ceebc47a..00000000 --- a/SwordOffer/src/web/T64.java +++ /dev/null @@ -1,42 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 滑动窗口的最大值 - * @author: mf - * @create: 2020/01/17 23:07 - */ - -import java.util.ArrayList; -import java.util.Collections; - -/** - *给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值 - * 。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口, - * 他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: - * {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。 - */ -public class T64 { - public ArrayList maxInWindows(int [] num, int size) { - if (null == num || size < 0) { - return null; - } - ArrayList list = new ArrayList<>(); - if(size == 0) { - return list; - } - int length = num.length; - ArrayList temp = null; - if (length < size) { - return list; - } else { - for (int i = 0; i < length - size + 1; i++) { - temp = new ArrayList<>(); - for (int j = i; j < size + i; j++) { - temp.add(num[j]); - } - Collections.sort(temp); - list.add(temp.get(temp.size() - 1)); - } - } - return list; - } -} diff --git a/SwordOffer/src/web/T65.java b/SwordOffer/src/web/T65.java deleted file mode 100644 index f2a60cc3..00000000 --- a/SwordOffer/src/web/T65.java +++ /dev/null @@ -1,49 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 矩阵中的路径 - * @author: mf - * @create: 2020/01/17 23:43 - */ - -/** - * 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。 - * 路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。 - * 如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 - * 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径, - * 但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 - */ -public class T65 { - public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { - if (null == matrix || matrix.length == 0 || null == str || str.length == 0 || matrix.length != rows * cols || rows <= 0 || cols <= 0){ - return false; - } - boolean[] visited = new boolean[rows * cols]; - int[] pathLength = {0}; - for (int i = 0; i <= rows - 1; i++){ - for (int j = 0; j <= cols - 1; j++){ - if (hasPathCore(matrix, rows, cols, str, i, j, visited, pathLength)) { - return true; - } - } - } - return false; - } - - public boolean hasPathCore(char[] matrix, int rows, int cols, char[] str,int row,int col, boolean[] visited,int[] pathLength) { - boolean flag = false; - - if (row >= 0 && row < rows && col >= 0 && col < cols && !visited[row * cols + col] && matrix[row * cols + col] == str[pathLength[0]]) { - pathLength[0]++; - visited[row * cols + col] = true; - if (pathLength[0] == str.length) { - return true; - } - flag = hasPathCore(matrix, rows, cols, str, row, col + 1, visited, pathLength) || hasPathCore(matrix, rows, cols, str, row + 1, col, visited, pathLength) || hasPathCore(matrix, rows, cols, str, row, col - 1, visited, pathLength) || hasPathCore(matrix, rows, cols, str, row - 1, col, visited, pathLength); - if (!flag) { - pathLength[0]--; - visited[row * cols + col] = false; - } - } - return flag; - } -} diff --git a/SwordOffer/src/web/T66.java b/SwordOffer/src/web/T66.java deleted file mode 100644 index b4b76e09..00000000 --- a/SwordOffer/src/web/T66.java +++ /dev/null @@ -1,32 +0,0 @@ -package web; - -/** - * @program LeetNiu - * @description: 机器人的运动范围 - * @author: mf - * @create: 2020/01/17 23:46 - */ - -public class T66 { - public int movingCount(int threshold, int rows, int cols) { - boolean[][] visited = new boolean[rows][cols]; - return countingStep(threshold,rows,cols,0,0,visited); - } - - public int countingStep(int limit, int rows, int cols, int r, int c, boolean[][] visited) { - if (r < 0 || r >= rows || c < 0 || c >= cols || visited[r][c] || bitSum(r) + bitSum(c) > limit) { - return 0; - } - visited[r][c] = true; - return countingStep(limit,rows,cols,r - 1,c,visited) + countingStep(limit,rows,cols,r,c - 1,visited)+countingStep(limit,rows,cols,r+1,c,visited)+countingStep(limit,rows,cols,r,c+1,visited) + 1; - } - - public int bitSum(int t) { - int count = 0; - while (t != 0) { - count += t % 10; - t /= 10; - } - return count; - } -} diff --git a/SwordOffer/src/web/T67.java b/SwordOffer/src/web/T67.java deleted file mode 100644 index ca593709..00000000 --- a/SwordOffer/src/web/T67.java +++ /dev/null @@ -1,35 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 剪绳子 - * @author: mf - * @create: 2020/01/17 23:47 - */ - -/** - * 给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。 - * 请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 - */ -public class T67 { - // 动态规划 - public int cutRope(int target) { - if (target < 2) return 0; - if (target == 2) return 1; - if (target == 3) return 2; - int[] products = new int[target + 1]; - products[0] = 0; - products[1] = 1; // 长度为2... - products[2] = 2; // 长度为3... - products[3] = 3; // 长度为4... - int max = 0; - for (int i = 4; i <= target; i++) { - max = 0; - for (int j = 1; j <= i / 2; j++) { - int product = products[j] * products[i - j]; - max = max > product ? max : product; - products[i] = max; - } - } - max = products[target]; - return max; - } -} diff --git a/SwordOffer/src/web/T7.java b/SwordOffer/src/web/T7.java deleted file mode 100644 index 16a53928..00000000 --- a/SwordOffer/src/web/T7.java +++ /dev/null @@ -1,28 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 斐波那契数列 - * @author: mf - * @create: 2020/01/09 13:28 - */ - -/** - *大家都知道斐波那契数列,现在要求输入一个整数n, - * 请你输出斐波那契数列的第n项(从0开始,第0项为0)。 - * n<=39 - * 思路: - * 递归,重复项太多,自底向上 - */ -public class T7 { - public int Fibonacci(int n) { - // 条件 - if (n <= 1) return n; - int pre2 = 0, pre1 = 1; - int f = 0; - for (int i = 2; i <= n; i++) { - f = pre2 + pre1; - pre2 = pre1; - pre1 = f; - } - return f; - } -} diff --git a/SwordOffer/src/web/T8.java b/SwordOffer/src/web/T8.java deleted file mode 100644 index eec02aae..00000000 --- a/SwordOffer/src/web/T8.java +++ /dev/null @@ -1,27 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 跳台阶 - * @author: mf - * @create: 2020/01/09 13:32 - */ - -/** - * 一只青蛙一次可以跳上1级台阶,也可以跳上2级。 - * 求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。 - * 思路: - * 递归,自底向上 - */ -public class T8 { - public int JumpFloor(int target) { - // 条件 - if (target <= 2) return target; - int pre2 = 1, pre1 = 2; - int sum = 0; - for (int i = 3; i <= target; i++) { - sum = pre2 + pre1; - pre2 = pre1; - pre1 = sum; - } - return sum; - } -} diff --git a/SwordOffer/src/web/T9.java b/SwordOffer/src/web/T9.java deleted file mode 100644 index fb9fa877..00000000 --- a/SwordOffer/src/web/T9.java +++ /dev/null @@ -1,15 +0,0 @@ -package web; /** - * @program LeetNiu - * @description: 变态跳台阶 - * @author: mf - * @create: 2020/01/09 13:34 - */ - -/** - * 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 - */ -public class T9 { - public int JumpFloorII(int target) { - return 1 << (target - 1); - } -} diff --git a/SwordOffer/src/web/TreeLinkNode.java b/SwordOffer/src/web/TreeLinkNode.java deleted file mode 100644 index 758a3118..00000000 --- a/SwordOffer/src/web/TreeLinkNode.java +++ /dev/null @@ -1,23 +0,0 @@ -package web; - -/** - * @program LeetNiu - * @description: - * @author: mf - * @create: 2020/01/16 14:23 - */ - -public class TreeLinkNode { - - int val; - - TreeLinkNode left = null; - - TreeLinkNode right = null; - - TreeLinkNode next = null; - - public TreeLinkNode(int val) { - this.val = val; - } -} diff --git a/SwordOffer/src/web/TreeNode.java b/SwordOffer/src/web/TreeNode.java deleted file mode 100644 index d0e0f844..00000000 --- a/SwordOffer/src/web/TreeNode.java +++ /dev/null @@ -1,21 +0,0 @@ -package web; - -/** - * @program LeetNiu - * @description: 树节点 - * @author: mf - * @create: 2020/01/08 16:59 - */ - -public class TreeNode { - - int val; - - TreeNode left; - - TreeNode right; - - public TreeNode(int val) { - this.val = val; - } -} diff --git a/Tools/books/books.md b/Tools/books/books.md deleted file mode 100644 index 5c4017bb..00000000 --- a/Tools/books/books.md +++ /dev/null @@ -1,28 +0,0 @@ -## 书单 - -### Java书籍(初学) - -- Head First Java(第二版·中文版):豆瓣评分8.7,''而且不只是读死书,你还会玩游戏、拼图、解谜题以及以意想不到的方式与Java交互''->还行,非常适合初学者。 - -- Java核心技术·卷 I(原书第10版):豆瓣评分8.3,"学习Java有两部很好的书籍:《Thinking in Java》和《Core Java》。前者内容丰富,在讲述Java语言的同时把Java编程思想也娓娓道来,让人如沐春风;后者偏重于讲解Java技术,举例说明了大量API的使用。这两部书在讲解Java上殊途同归,都值得放在书桌上时常翻阅。" - -### Java书籍(多线程) - -- Java并发编程之美:豆瓣评分8.4:针对于初学者,比较推荐这本书的,基础概念都是点到为止,并不是云里雾里,里面的源码值得多看看。 -- Java并发编程的艺术:豆瓣评分7.4:可以啃这本书,这本书的最大特点就是概念多并且非常详细,但内容偏旧了。 - -### Java书籍(虚拟机) - -- 深入理解Java虚拟机(第二版):豆瓣评分9.0,简直理解虚拟机是神书哇,多看几遍,收获挺大的。 -- - -### Java书籍(Web) - -- 深入分析Java Web技术内幕(修订版):豆瓣评分7.5,包括:CDN 动态加速、多终端化改造、 多终端Session 统一 ,以及在大流量的情况下,如何跨越性能、网络和一个地区的电力瓶颈等内容,并提供了比较完整的解决方案。 - -- - -## 视频 - -- [基础-毕向东Java基础教程](https://www.bilibili.com/video/BV1pt41127na?from=search&seid=15181309342578712918) -- \ No newline at end of file diff --git "a/Tools/dubbo/centos7\345\256\211\350\243\205dubbo\347\216\257\345\242\203.md" "b/Tools/dubbo/centos7\345\256\211\350\243\205dubbo\347\216\257\345\242\203.md" deleted file mode 100644 index 59c445b4..00000000 --- "a/Tools/dubbo/centos7\345\256\211\350\243\205dubbo\347\216\257\345\242\203.md" +++ /dev/null @@ -1,87 +0,0 @@ -## 引言 - -> 最近看了看分布式架构,尝试了一下dubbo,注册中心是hiZookeeper,所以先在centos下配置环境吧。 - - - -## 前提 - -- 系统:Centos7 - -- 安装[Java](http://dreamcat.ink/2019/07/08/windows-mac-he-linux-an-zhuang-java/)环境 - -- [下载地址](https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) - -- 选择Linux 64位tar.gz - -- 我一般将它放进`/usr/local/myapps`,myapps是存放自己的软件等。 - -- 解压`tar -xzvf xxx`,xxx指的刚才下载的jdk - -- 添加java的环境变量,打开`vim /etc/profile` - -- 在蓝色字体下面添加 - -- ```bash - JAVA_HOME=/usr/local/myapps/jdk1.8.0_221 # 这是我的java存放的地址 - PATH=$JAVA_HOME/bin:$PATH - export JAVA_HOME PATH - ``` - -- 更新`source /etc/profile` - -- 查看是否添加成功`javac `即可 - -## 安装Zookeeper - -- [下载地址](https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/) -- 选择3.4.14版本下载即可,放进和刚才的myapps下。 -- 解压:`tar -zxf zookeeper-3.4.14.tar.gz` -- 将配置文件`cp -r zoo_sample.cfg zoo.cfg` -- 启动zookeeper`./zkServer.sh start` - -## 安装Dubbo - -Dubbo的源码[github](https://github.com/apache/dubbo/tree/2.5.x) - -我提前编译好的[dubbo.war](https://www.lanzous.com/i7xn2di). [dubbo-monitor-simple](https://www.lanzous.com/i7xn1ih) - -- 下载2.5.x版本的zip -- 前提是系统装了maven环境,可以编译生成war。 -- 解压分别进入dubbo-admin和Dubbo-simple->Dubbo-monitor-simple,执行`mvn package` -- 分别在target文件下找到了对应的一个war包和tar.gz包 -- 解压 `tar -zxf dubbo-monitor-simple-2.0.0-assembly.tar.gz` -- 进入配置文件修改`vim dubbo.properties` -- 将注册中心#去掉,`dubbo.registry.address=zookeeper://127.0.0.1:2181` -- 将multicast注册中心加# -- 如果是低配置云服务器,记得修改`vim start.sh`的内存大小,512m,原来2g -- 然后启动dubbo`./start.sh start` -- 记得防火墙设置,可以加端口,也可以直接关掉防火墙 - - 加端口:如果在`/etc/sysconfig/iptables`,那默认是firewall防火墙,所以卸载 - - `systemctl stop firewalld ` - - `systemctl mask firewalld` - - `yum install -y iptables` - - `yum install iptables-services` - - 开启服务`systemctl start iptables.service` - - 设置防火墙启动`systemctl enable iptables.service` - - 然后执行`vim /etc/sysconfig/iptables`模仿22端口添加即可 - - 记得添加2181,8080 - -## 安装tomcat - -由于dubbo.war在tomcat容器中运行,所以先下载tomcat,其实tomcat不用安装,下载解压即可。 - -- [下载地址](http://tomcat.apache.org/) -- 选择左边栏中的**tomcat8** -- 选择core中的**tar.gz**下载 -- 然后放在`myapps`路径下:`/usr/local/myapps`下 -- 解压:`tar -zxf apache-tomcat-8.5.47` -- 修改端口`cd conf` -- `vim server.xml`将8080改为8088,因为不改的话会和dubbo冲突 -- 将webapps下的ROOT目录下的文件全部删除`rm -rf ./*`即可 -- 将dubbo.war存放在刚才的ROOT目录下 -- 解压:`unzip dubbo.war -d apache-tomcat-8.5.47/webapps/ROOT/` -- 如果提示没有unzip的话,安装`yum install -y unzip zip` -- 去bin目录下启动`./startup.sh` -- 记得添加防火墙 -- 访问对应的ip加端口:8088之后,guest账户和密码guest,root账户和密码是root \ No newline at end of file diff --git "a/Tools/frp/\345\205\250\347\275\221\346\234\200\345\205\250frp\345\206\205\347\275\221\347\251\277\351\200\217(ssh\345\217\212web).md" "b/Tools/frp/\345\205\250\347\275\221\346\234\200\345\205\250frp\345\206\205\347\275\221\347\251\277\351\200\217(ssh\345\217\212web).md" deleted file mode 100644 index 574f6f36..00000000 --- "a/Tools/frp/\345\205\250\347\275\221\346\234\200\345\205\250frp\345\206\205\347\275\221\347\251\277\351\200\217(ssh\345\217\212web).md" +++ /dev/null @@ -1,203 +0,0 @@ -## 1. 准备工作 - -- vps(云服务器一台) -- 访问目标设备(就是你最终要访问的设备) -- 简单的Linux基础(会用cp等几个简单命令即可) - - - -## 2. 下载frp - -- [frp-github]() -- 选择release中对应的版本 -- 比如linux:[frp_0.27.0_linux_amd64.tar.gz](https://github.com/fatedier/frp/releases/download/v0.27.0/frp_0.27.0_linux_amd64.tar.gz) - - - -## 3. 配置frp - -### 1. 简单介绍 - -- frps(服务端启动) -- frps.ini(服务器启动配置文件) -- frpc(客户端启动) -- frpc.ini(客户端启动配置文件) -- 配置前先备份哦`cp` - -### 2. 服务端 - -1. `vim frps.ini` - -2. 有以下内容 - - ```shell - [common] - bind_port = 7000 - dashboard_port = 7500 - token = 12345678 - dashboard_user = admin - dashboard_pwd = admin - ``` - - - “bind_port”表示用于客户端和服务端连接的端口,这个端口号我们之后在配置客户端的时候要用到。 - - “dashboard_port”是服务端仪表板的端口,若使用7500端口,在配置完成服务启动后可以通过浏览器访问 x.x.x.x:7500 (其中x.x.x.x为VPS的IP)查看frp服务运行信息。 - - “token”是用于客户端和服务端连接的口令,请自行设置并记录,稍后会用到。 - - “dashboard_user”和“dashboard_pwd”表示打开仪表板页面登录的用户名和密码,自行设置即可。 - -3. `./frps -c frps.ini` - -4. 若出现以下内容 - - ```shell - 2019/01/12 15:22:39 [I] [service.go:130] frps tcp listen on 0.0.0.0:7000 - 2019/01/12 15:22:39 [I] [service.go:172] http service listen on 0.0.0.0:10080 - 2019/01/12 15:22:39 [I] [service.go:193] https service listen on 0.0.0.0:10443 - 2019/01/12 15:22:39 [I] [service.go:216] Dashboard listen on 0.0.0.0:7500 - 2019/01/12 15:22:39 [I] [root.go:210] Start frps success - ``` - - - 此时访问 x.x.x.x:7500 并使用自己设置的用户名密码登录,即可看到仪表板界面 - -5. 后台运行`nohup ./frps -c frps.ini &` - -### 3. 客户端 - -1. `vim frpc.ini` - -2. 有以下内容 - - ```shell - [common] - server_addr = x.x.x.x # 服务器地址 - server_port = 7000 # 和服务器端口对应 - token = 12345678 # 和服务器token对应 - [ssh] - type = tcp - local_ip = 127.0.0.1 - local_port = 22 - remote_port = 2222 - ``` - - - “server_addr”为服务端IP地址,填入即可。 - - “server_port”为服务器端口,填入你设置的端口号即可,如果未改变就是7000 - - “token”是你在服务器上设置的连接口令,原样填入即可。 - -3. `./frpc -c frpc.ini` - -4. 后台运行如服务器同上 - -## 映射web项目的端口 - -### 服务端 - -`vim frps.ini` - -```shell -[common] -bind_port = 6000 -token = mai -vhost_http_port = 2020 # 这里很重要哈 -``` - -`./frps -c frps.ini` - -### 客户端 - -`vim frpc.ini` - -```shell -[common] -server_addr = 39.108.xx.xxx -server_port = 6000 -token = mai - -#[ssh] -#type = tcp -#local_ip = 127.0.0.1 -#local_port = 22 -#remote_port = 6000 - -# 举例第一个 -[web-flask] -type = http -local_port = 5000 -custom_domains = flask.dreamcat.ink - -# 举例第二个 -[web-flask2] -type = http -local_port = 5001 -custom_domains = flask2.dreamcat.ink -``` - -`./frpc -c frpc.ini` - -**注意:custom_domains的域名需要去域名系统解析上述外网地址** - -**访问:flask.dreamcat.ink** - -**访问:flas2.dreamcat.ink** - -## 4. 开机自启 - -### 1. 第一种 - -最简单粗暴的方式直接在脚本`/etc/rc.d/rc.local`(和`/etc/rc.local`是同一个文件,软链)末尾添加自己的**脚本** -然后,增加脚本执行权限。 - -```shell -nohup /home/dsp/config/frp/frpc -c /home/dsp/config/frp/frpc.ini & -``` - - - -```shell -chmod +x /etc/rc.d/rc.local -``` - -### 2. 第二种 - -```shell -crontab -e -@reboot /home/user/test.sh -``` - -### 每次登陆自动执行 - -也可以设置每次登录自动执行脚本,在`/etc/profile.d/`目录下新建sh脚本, -`/etc/profile`会遍历`/etc/profile.d/*.sh` - -### 第三种 - -**压缩包中有systemd,可利用这个服务开机自启** - -**比如,将frps.server复制到`etc/systemd/system/`** - -```shell -[Unit] -Description=Frp Server Service -After=network.target - -[Service] -Type=simple -User=nobody -Restart=on-failure -RestartSec=5s -ExecStart=/usr/bin/frps -c /etc/frp/frps.ini ##这里记得对应的路径 - -[Install] -WantedBy=multi-user.target -``` - -**接着可以利用systemd命令,比如** - -```shell -systemctl start frps #启动 -systemctl stop frps #停止 -systemctl restart frps #重启 -systemctl status frps #查看状态 -systemctl enable frps #开机启动frp -systemctl disable frps # 禁止启动 -``` - - diff --git "a/Tools/idea/idea\345\270\270\347\224\250\351\205\215\347\275\256.md" "b/Tools/idea/idea\345\270\270\347\224\250\351\205\215\347\275\256.md" deleted file mode 100644 index ce565353..00000000 --- "a/Tools/idea/idea\345\270\270\347\224\250\351\205\215\347\275\256.md" +++ /dev/null @@ -1 +0,0 @@ -- [idea配置class反编译工具](https://www.itread01.com/content/1549811589.html) \ No newline at end of file diff --git "a/Tools/network/Linux\345\221\275\344\273\244\350\241\214\347\232\204\345\245\207\346\267\253\346\212\200\345\267\247.md" "b/Tools/network/Linux\345\221\275\344\273\244\350\241\214\347\232\204\345\245\207\346\267\253\346\212\200\345\267\247.md" deleted file mode 100644 index 5f2dc82a..00000000 --- "a/Tools/network/Linux\345\221\275\344\273\244\350\241\214\347\232\204\345\245\207\346\267\253\346\212\200\345\267\247.md" +++ /dev/null @@ -1,99 +0,0 @@ -## 引言 - -**常用的命令行技巧...** - - -## 前言 - -- 主要针对常用的一些命令行的使用 -- 主要是unix的操作系统 - - - -## 常用 - -- `cd`:这个就不需要多讲了 -- `clear`:这个也不需要多讲了 -- `mkdir`:当前目录创建文件夹 -- `touch`:当前目录下创建文件 -- `ls`:查看目录下的文件 - - `ls -a`:查看文件,包括隐藏文件 - - `ls -l`:详细查看文件 -- `top`:查看cpu和内存等 -- `df -h`:查看各个磁盘使用的状态 -- `du -hd1`:查看当前目录下文件的大小 -- `du -h`:查看当前目录下文件的大小,包括子目录 -- `nautilus ./`:打开当前文件管理器 -- `pwd`:查看当前路径 -- `w`:查看机器运行的时间 - -- `lsof -i:8000`:查看端口占用情况 -- `ps -aux | grep python`: 查看某个进程 -- `ps -ef | grep python`: 查看某个进程 - -## 统计文件数目 - -### ls - -`ls -l | wc -l`计数当前目录的文件和文件夹。 它会计算所有的文件和目录。 - -`ls -la | wc -l`统计当前目录包含隐藏文件和目录在内的文件和文件夹。 - -### find - -`find . -type f | wc -l`递归计算当前目录的文件,包括隐藏文件。 - -`find . type d | wc -l`递归计算包含隐藏目录在内的目录数。 - -`find . -name '*.txt' | wc -l`根据文件扩展名计数文件数量。 这里我们要计算 `.txt` 文件。 - -## 日常使用 - -- 可以通过**Tab**键实现自动补全参数 -- 使用**ctrl-r**搜索命令行的历史记录 -- 按下**ctrl-w**删除键入的最后的一个单词 -- 按下**ctrl-u**可以删除行内光标所在位置之前的内容,**alt-b**和**alt-f**可以在以单词为单位移动光标,**ctrl-a**可以将光标移至行首,**ctrl-e**可以将光标移至行尾 -- 回到前一个工作路径:`cd -` -- `pstree -p`以一种优雅的方式展示进程树 -- `kill -STOP[pid]`停止一个进程 -- 使用`nohup`或`disown`使一个后台进程持续运行 - - eg:`nohup python -u demo.py > ./logs/demo.log 2>&1 &` -- 使用`netstat -lntp`检查哪些进程在监听端口 -- 使用`uptime`或`w`查看系统已经运行对长时间 -- 使用`alias`来常见常用命令的快捷形式,例如:`alias ll='ls -latr'`创建了一个新的命令别名,可以把别名放在`~./bashrc` - -## 文件及数据处理 - -- 在当前目录下通过文件名查找一个文件,使用类似于这样的命令:`find . -name '*something'` -- 使用`wc`去计算新行数`-l`,字符数`-m`,单词数`-w`以及字节数`-c`,例如`ls | wc -l` 、`ls -lR | grep "^-" | wc -l` -- `du -sh *`查看某个文件的大小 -- `du -h --max-depth=1`查看当前目下文件的大小 -- `du -hd1`查看当前目录下文件的大小--mac -- `df -hl` 查看磁盘情况 - - - -## 和服务器交互 - -- 下载文件`scp username@servername:/path/filename /var/www/local_dir(本地目录)` -- 上传文件`scp /path/filename username@servername:/path` -- 下载文件夹`scp -r username@servername:/path/filename /var/www/local_dir(本地目录)` -- 下载文件夹`scp -r /path/filename username@servername:/path` -- 若是更改端口, 则前面 加上-p - - - -## bash互换 - -- zsh切bash`chsh -s /bin/bash` -- bash切zsh`chsh -s /bin/zsh` - -## 有一些挺有用 - -- `bc`计算器 -- `cal`漂亮的日历 -- `tree`以树的形式显示路径和文件 -- `watch`重复运行同一个命令,展示结果有更改的部分 - -## 持续补充... - diff --git "a/Tools/network/github\350\241\250\346\203\205\345\233\276\346\240\207.md" "b/Tools/network/github\350\241\250\346\203\205\345\233\276\346\240\207.md" deleted file mode 100644 index 8a0b0a10..00000000 --- "a/Tools/network/github\350\241\250\346\203\205\345\233\276\346\240\207.md" +++ /dev/null @@ -1,41 +0,0 @@ -## 引言 - -> github的一些表情图标的含义,方便做文档,从而显得有规范,高逼格一点。 -> -> 放一个emoji网站:[emoji参考](https://www.webfx.com/tools/emoji-cheat-sheet/) - - - -| emoji | emoji代码 | commit提交说明 | -| :-------------------------------------: | :--------------------------: | :-------------------: | -| :art: (调色板) | `:art:` | 改进代码结构/代码格式 | -| :zap: (闪电):racehorse: (赛马) | `:zap: :racehorse:` | 提升性能 | -| :fire: (火焰) | `:fire:` | 移除代码或文件 | -| :bug: (bug) | `:bug:` | 修复 bug | -| :ambulance: (急救车) | `:ambulance:` | 重要补丁 | -| :sparkles: (火花) | `:sparkles:` | 引入新功能 | -| :memo: (备忘录) | `:memo:` | 撰写文档 | -| :rocket: (火箭) | `:rocket:` | 部署功能 | -| :lipstick: (口红) | `:lipstick:` | 更新 UI 和样式文件 | -| :tada: (庆祝) | `:tada:` | 初次提交 | -| :white_check_mark: (白色复选框) | `:white_check_mark:` | 增加测试 | -| :lock: (锁) | `:lock:` | 修复安全问题 | -| :apple: (苹果) | `:apple:` | 修复 macOS 下的问题 | -| :penguin: (企鹅) | `:penguin:` | 修复 Linux 下的问题 | -| :checkered_flag: (旗帜) | `:checked_flag:` | 修复 Windows 下的问题 | -| :bookmark: (书签) | `:bookmark:` | 发行/版本标签 | -| :rotating_light: (警车灯) | `:rotating_light:` | 移除 linter 警告 | -| :construction: (施工) | `:construction:` | 工作进行中 | -| :green_heart: (绿心) | `:green_heart:` | 修复 CI 构建问题 | -| :arrow_down: (下降箭头) | `:arrow_down:` | 降级依赖 | -| :arrow_up: (上升箭头) | `:arrow_up:` | 升级依赖 | -| :construction_worker: (工人) | `:construction_worker:` | 添加 CI 构建系统 | -| :chart_with_upwards_trend: (上升趋势图) | `:chart_with_upwards_trend:` | 添加分析或跟踪代码 | -| :hammer: (锤子) | `:hammer:` | 重大重构 | -| :heavy_minus_sign: (减号) | `:heavy_minus_sign:` | 减少一个依赖 | -| :whale: (鲸鱼) | `:whale:` | Docker 相关工作 | -| :heavy_plus_sign: (加号) | `:heavy_plug_sign:` | 增加一个依赖 | -| :wrench: (扳手) | `:wrench:` | 修改配置文件 | -| :globe_with_meridians: (地球) | `:globe_with_meridians:` | 国际化与本地化 | -| :pencil2: (铅笔) | `:pencil2:` | 修复 typo | - diff --git "a/Tools/network/\345\274\200\346\272\220\347\232\204github\351\241\271\347\233\256\346\200\273\347\273\223.md" "b/Tools/network/\345\274\200\346\272\220\347\232\204github\351\241\271\347\233\256\346\200\273\347\273\223.md" deleted file mode 100644 index 1448e83a..00000000 --- "a/Tools/network/\345\274\200\346\272\220\347\232\204github\351\241\271\347\233\256\346\200\273\347\273\223.md" +++ /dev/null @@ -1,131 +0,0 @@ -## 引言 - -> **一些有趣的github项目总结,其中包括终端、Python、Java、笔试&面试、效率软件等。** - -## 终端 - -- [The Art of Command Line]() :系统的学习命令行的用法。**star:57509** -- [oh-my-zsh](): 这玩意不用我简单介绍了吧?**star:90516** -- [git-tips](https://github.com/521xueweihan/git-tips) Git的奇技淫巧 **star:11400** -- [powerline-fonts]() : mac挺好用的一款终端字体。**star:169** -- [powerlevel9k]() : oh-my-zsh的一款皮肤。**star:9952** -- [zsh-syntax-highlighting]() : 终端输入的命令有语法高亮效果。**star:7326** -- [zsh-autosuggestions]() : 终端代码补全插件。**star:6662** - - - -## Python - -- [awesome-python-login-model]() : 模拟登陆一些大型网站的demo,个人觉得不错,值得学习。**star:8403** -- [pyppeteer]() : 模拟动态加载js,比selenium稍微好用一些。**star:1924** -- [requests]() :Python HTTP Requests for Humans™ ✨🍰✨ **star:39860** -- [requests-html]() : Pythonic HTML Parsing for Humans™ **star:10111** -- [httpx]() A next generation HTTP client for Python. 🦋 **star:1900** -- [PySimpleGUI]() : 做一些简单的GUI,可以用这个,简单应用。**star:1608** -- [bokeh]() Interactive Web Plotting for Python。 **star:10701** -- [wxpy]() 微信机器人 / 可能是最优雅的微信个人号 API ✨✨ [http://wxpy.readthedocs.io](http://wxpy.readthedocs.io/) **star:10700** - -## Java - -- [Awesome Java]() A curated list of awesome frameworks, libraries and software for the Java programming language. **star:21651** - -### 1. 后端 - -- [spring-boot-examples]() : 对于初学者学习Spring-boot,是个非常不错的例子。**star:16408** -- [SpringAll]()循序渐进,学习Spring Boot、Spring Boot & Shiro、Spring Cloud、Spring Security & Spring Security OAuth2,博客Spring系列源码。**star:6181** -- [interest]() : Vue+Spring boot前后端分离博客项目。**star:494** -- [litemall]() : 一个小商城。litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端。**star:7586** -- [vhr]()微人事是一个前后端分离的人力资源管理系统,项目采用SpringBoot+Vue开发。**star:4705** -- [mybatis]() MyBatis SQL mapper framework for Java **star:11335** -- [miaosha]() ⭐⭐⭐⭐秒杀系统设计与实现.互联网工程师进阶与分析🙋🐓 **star:9400** -- [spring-boot-plus]()🔥spring-boot-plus集成Spring Boot 2.1.6,Mybatis,Mybatis Plus,Druid,FastJson,Redis,Rabbit MQ,Kafka等,可使用代码生成器快速开发项目. **star:551** -- [hope-boot]() 🌱🚀一款现代化的脚手架项目。🍻整合Springboot2 **star:1543** -- [spring-boot-demo]() spring boot demo 是一个用来学习 spring boot 的项目,总共包含 57 个集成demo,已经完成 47 个。**star:2149** -- [spring-boot-api-project-seed]() 🌱🚀一个基于Spring Boot & MyBatis的种子项目,用于快速构建中小型API、RESTful API项目~ **star:1543** -- [White-Jotter](): 白卷是一款使用 Vue+Spring Boot 开发的前后端分离的图书管理项目 **star:115** -- [eladmin]() 项目基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统,项目采用分模块开发方式, 权限控制采用 RBAC,支持数据字典与数据权限管理,支持一键生成前后端代码,支持动态路由 **start:3163** -- [dbblog]() 基于SpringBoot2.x+Vue2.x+ElementUI+Iview+Elasticsearch+RabbitMQ+Redis+Shiro的多模块前后端分离的博客项目 **star:375** -- [spring-analysis]() Spring源码阅读 **star:4045** -- [mall]() mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。**star:21926** -- [后端架构师技术图谱](https://github.com/xingshaocheng/architect-awesome) **star:42900** -- [mall-swarm](https://github.com/macrozheng/mall-swarm)mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich、Spring Boot 2、MyBatis、Docker、Elasticsearch等核心技术,同时提供了基于Vue的管理后台方便快速搭建系统。**star:1400** -- [jeecg-boot](https://github.com/zhangdaiscott/jeecg-boot)一款基于代码生成器的JAVA快速开发平台,开源界“小普元”超越传统商业企业级开发平台!**star:9600** -- [dbblog](https://github.com/llldddbbb/dbblog) 基于SpringBoot2.x+Vue2.x+ElementUI+Iview+Elasticsearch+RabbitMQ+Redis+Shiro的多模块前后端分离的博客项目 [http://www.dblearn.cn](http://www.dblearn.cn/) **star:643** -- [ springboot-seckill](https://github.com/zaiyunduan123/springboot-seckill) 基于SpringBoot + MySQL + Redis + RabbitMQ + Guava开发的高并发商品限时秒杀系统 **star:943** -- [MeetingFilm](https://github.com/daydreamdev/MeetingFilm)基于微服务架构的在线电影购票平台 **star:111** -- [guava](https://github.com/google/guava) Google core libraries for Java **star:36400** -- [hutool](https://github.com/looly/hutool) A set of tools that keep Java sweet. [http://www.hutool.cn](http://www.hutool.cn/) **star:10800** - -### 2. 笔试&&面试 - -- [JavaGuide]() :一份涵盖大部分Java程序员所需要掌握的核心知识。**star:45159** -- [advanced-java]() :互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。**star:22747** -- [LeetCodeAnimation]() : LeetCode用动画的形式的呈现。**star:32650** -- [technology-talk]() 汇总java生态圈常用技术框架等。**star:6229** -- [interview_internal_reference]() 2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。 **star:14335** -- [interviews]() Everything you need to know to get the job. **star:37598** -- [interview_internal_reference]()2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。 **star:15570** -- [leetcode]() LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。) -- [reverse-interview-zh]() 技术面试最后反问面试官的话 **star:4500** -- [algo](https://github.com/wangzheng0822/algo) 数据结构和算法必知必会的50个代码实现 **star:10700** -- [fucking-algorithm](https://github.com/labuladong/fucking-algorithm) 手把手撕LeetCode题目,扒各种算法套路的裤子,not only how,but also why. English version supported! https://labuladong.gitbook.io/algo/ **star:4700** -- [JavaFamily](https://github.com/AobingJava/JavaFamily)【互联网一线大厂面试+学习指南】进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,作者风格幽默,看起来津津有味,把学习当做一种乐趣,何乐而不为,后端同学必看,前端同学我保证你也看得懂,看不懂你加我微信骂我渣男就好了。 **star:7100** -- [Java-Interview](https://github.com/gzc426/Java-Interview) Java 面试必会 直通BAT **star:3500** - -## Vue&&前端 - -- [awesome-vue]() awesome things related to Vue.js **star:46634** -- [vue-form-making]()基于Vue的表单设计器,让表单开发简单而高效。 **star:1347** -- [fe-interview]() 前端面试每日 3+1,以面试题来驱动学习,提倡每日学习与思考,每天进步一点!每天早上5点纯手工发布面试题(死磕自己,愉悦大家)**star:6667** -- [vue2-elm]() 基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用 **star:30300** -- [Web]() 前端入门和进阶学习笔记,超详细的Web前端学习图文教程。从零开始学前端,做一名精致的前端工程师。持续更新… **star:55300** -- [javascript-algorithms]() Algorithms and data structures implemented in JavaScript with explanations and links to further readings **star:55277** -- [nodebestpractices]() ✅ The largest Node.js best practices list (September 2019) **star:35000** -- [gods-pen](https://github.com/ymm-tech/gods-pen) 基于vue的高扩展在线网页制作平台,可自定义组件,可添加脚本,可数据统计。A mobile page builder/editor, similar with amolink. [https://godspen.ymm56.com](https://godspen.ymm56.com/) **star:1200** -- [Daily-Interview-Question](https://github.com/Advanced-Frontend/Daily-Interview-Question) 我是木易杨,公众号「高级前端进阶」作者,每天搞定一道前端大厂面试题,祝大家天天进步,一年后会看到不一样的自己。**star:17000** - -## 面试 - -- [CS-Notes]() : 技术面试必备基础知识、Leetcode 题解、Java、C++、Python、后端面试、操作系统、计算机网络、系统设计。**star:67433** - -## Flutter - -- [Flutter_YYeTs]() : 基于Flutter的人人影视移动端。**star:233** -- [Flutter-Notebook]() 日更的FlutterDemo合集,今天你fu了吗 **star:3879** -- [Best-Flutter-UI-Templates]() completely free for everyone. Its build-in Flutter Dart. **star:1261** - -## 效率软件 - -- [frp]() : 内网穿透,你懂的。**star:24539** -- [musicbox]() : 网易云音乐命令行版本。**star:7601** -- [motrix]() : 配合百度云有着奇淫技巧。 **star:11098** -- [Electronic WeChat]() : 该项目虽然不再维护,但挺好用的(linux)。**star:12622** -- [hexo]() : 搭建博客系统框架,挺好用。**star:26868** -- [awesome-mac]()推荐的非常给力且实用。 **star:29814** -- [蓝灯]() 免费的vpn。 **star:9520** -- [LazyDocker]() The lazier way to manage everything docker. **star:10906** -- [postwoman]() 👽 API request builder - Helps you create your requests faster, saving you precious time on your development. **star:2190** -- [x64dbg]()An open-source x64/x32 debugger for windows. **star:34260** -- [google-access-helper]() 谷歌访问助手破解版 **star:3887** -- [v2ray-core]() A platform for building proxies to bypass network restrictions. **star:21444** -- [v2rayN]() vpn **star:1249** -- [Alfred-collection]() A collection of all known Alfred3 workflows **star:411** -- [RevokeMsgPatcher](https://github.com/huiyadanli/RevokeMsgPatcher) editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) **star:1500** -- [lx-music-desktop](https://github.com/lyswhut/lx-music-desktop) 一个基于 electron 的音乐软件 **star:1300** -- [UnblockNeteaseMusic](https://github.com/nondanee/UnblockNeteaseMusic) Revive unavailable songs for Netease Cloud Music **star:7900** -- [CodeVar](https://github.com/xudaolong/CodeVar) 生成可用的代码变量 (CodeVar that return u a better variable from Chinese to English . ) **star:684** - -## 有趣 - -- [ChineseBQB](ChineseBQB)🇨🇳Chinese sticker pack,More joy / 中国表情包大集合,更欢乐 **star:2319** -- [free-programming-books-zh_CN]()📚 免费的计算机编程类中文书籍,欢迎投稿 **star:55296** -- [freeCodeCamp]() The [https://www.freeCodeCamp.org](https://www.freecodecamp.org/) open source codebase and curriculum. Learn to code for free together with millions of people. **star:304920** -- [hosts]() 镜像 **star:15582** -- [free-api]() 收集免费的接口服务,做一个api的搬运工 **star:6000** -- [fanhaodaquan](https://github.com/imfht/fanhaodaquan) 番号大全。 **star:1200* -- [BullshitGenerator](https://github.com/menzi11/BullshitGenerator) Needs to generate some texts to test if my GUI rendering codes good or not. so I made this. **star:3700** -- [howto-make-more-money](https://github.com/easychen/howto-make-more-money): 程序员如何优雅的挣零花钱 **star:8200** -- [marktext](https://github.com/marktext/marktext) A simple and elegant markdown editor, available for Linux, macOS and Windows. [https://marktext.app](https://marktext.app/) **star:14800** -- [FreePAC](https://github.com/xiaoming2028/FreePAC) 科学上网/梯子/自由上网/翻墙 SS/SSR/V2Ray/Brook 搭建教程 **star:2146** - -## 持续更新... \ No newline at end of file diff --git "a/Tools/network/\346\224\266\351\233\206\345\270\270\347\224\250\347\232\204\347\275\221\347\253\231(\346\214\201\347\273\255\346\233\264\346\226\260...).md" "b/Tools/network/\346\224\266\351\233\206\345\270\270\347\224\250\347\232\204\347\275\221\347\253\231(\346\214\201\347\273\255\346\233\264\346\226\260...).md" deleted file mode 100644 index dabffaed..00000000 --- "a/Tools/network/\346\224\266\351\233\206\345\270\270\347\224\250\347\232\204\347\275\221\347\253\231(\346\214\201\347\273\255\346\233\264\346\226\260...).md" +++ /dev/null @@ -1,48 +0,0 @@ -## 引言 - -> **收集一些常用的在线网站,包括在线工具、第三方视频、文章博客、磁力和软件等...** - -## 在线工具 - -- [在线JSON校验格式工具]() -- [正则表达式在线测试]() -- [RGB颜色查询对照表]() -- [在线正则表达式测试]() -- [UrlEncode编码解码]() -- [时间戳在线转换]() -- [草料二维码生成器]() -- [在线Java编辑器]() -- [MarkDown转HTML]() -- [在线图标下载]() -- [Greasy Fork 油猴脚本]() -- [科技小c]() -- [高清壁纸wall]() -- [在线图片尺寸大小修改]() -- [Unicode转中文](http://tool.chinaz.com/tools/unicode.aspx) -- [ppt模版下载](http://www.ypppt.com/) -- [CSDN自助下载](http://kuaitu888.com/) -- [4k壁纸](http://pic.netbian.com/) -- [xclient-mac软件下载]() -- [iterm2主题](https://sspai.com/post/53008) -- [常用的workflow](https://hufangyun.com/2018/alfred-workflow-recommend/) -- [mac软件下载-1](https://www.macenjoy.co/) -- [mac软件下载-2](https://xclient.info/) - -## 第三方视频 - -- [免费影视网站](https://hao.su/531/) -- [动漫](https://www.agefans.tv/) - -## BT磁力 - -- [磁力导航]() -- [网盘搜索]() -- [DogeDoge搜索]() -- [云盘精灵](https://www.yunpanjingling.com/) -- [盘天才](https://www.pantianxia.com/) - -## 其他 - -- [chromium-575458](https://npm.taobao.org/mirrors/chromium-browser-snapshots/) - - diff --git "a/Tools/rocketmq/centos7\345\256\211\350\243\205rocketmq\345\217\212\351\205\215\347\275\256.md" "b/Tools/rocketmq/centos7\345\256\211\350\243\205rocketmq\345\217\212\351\205\215\347\275\256.md" deleted file mode 100644 index 196110a5..00000000 --- "a/Tools/rocketmq/centos7\345\256\211\350\243\205rocketmq\345\217\212\351\205\215\347\275\256.md" +++ /dev/null @@ -1,80 +0,0 @@ -## 引言 -> 项目中用到消息队列RocketMQ,因此在Centos7安装及配置。。。 - - - -## 下载 - -- [rocketmq](https://mirrors.tuna.tsinghua.edu.cn/apache/rocketmq/) -- 选择**rocketmq-all-4.6.1-bin-release.zip** -- 因为RocketMQ依赖maven打包,因此需要安装maven -- 注意:下面所添加的环境统统是.zshrc - -## Maven安装 - -- [maven地址](http://mirrors.hust.edu.cn/apache/maven/maven-3/) - -- 在本文章中采用最新**3.6.3** - -- 解压`tar -zxf apache-maven-3.6.3-bin.tar.gz` - -- 修改仓库地址为阿里云,因为默认下载依赖总超时,找到conf中的setting.xml文件 - -- ```xml - - alimaven - aliyun maven - http://maven.aliyun.com/nexus/content/groups/public/ - central - - ``` - -- 配置环境变量`vim .zshrc` - -- ```shell - export M2_HOME=/home/pch/Documents/mf/web/maven3 - export PATH=$PATH:$Java_HOME/bin:$M2_HOME/bin - ``` - -- 刷新环境变量`source .zshrc` - -## Rocketmq安装 - -- 解压`unzip rocketmq-all-4.6.1-bin-release.zip -d ./` -- 注意在bin目录中:`runserver.sh和runbroker.sh` **个人情况修改JAVA_OPT="${JAVA_OPT} -server一行参数** -- 将nameserver地址添加到环境变量中 -- `export NAMESRV_ADDR=127.0.0.1:9876` -- **刷新配置文件`source .zshrh`** -- 创建logs文件夹,存放启动日志,方便查看 -- 后台运行nameserver`nohup sh mqnamesrv > ../logs/nameser.log 2>&1&` -- 后台运行broker`nohup sh mqbroker > ../logs/broker.log 2>&1&` - -## 控制台安装 - -**这个控制台属于springboot的项目...** - -- 项目链接`git clone https://github.com/apache/rocketmq-externals` - -- rocketmq-externals里面有所有Apache RocketMq外部项目,有的还在孵化中,我主要是使用rocketmq-console,进入到console项目中,修改resources文件夹下面的配置文件 - -- 在`rocketmq-externals/rocketmq-console/src/main/resources`目录下打开配置文件`vim application.properties` - -- 修改以下配置 - -- ```properties - server.port=8090 - rocketmq.config.namesrvAddr=127.0.0.1:9876 - rocketmq.config.dataPath=/home/pch/Documents/mf/web/rocket-data - ``` - -- 开始maven打包`mvn clean install -Dmaven.test.skip=true` - -- 完成之后在target找到`rocketmq-console-ng-1.0.1.jar`,后台运行它 - -- `nohup java -jar rocketmq-console-ng-1.0.1.jar > rocket-data/console.out 2>&1&` - -- `localhost:8090`即可看到效果 - -- ![](http://media.dreamcat.ink/20200222002600.png) - - diff --git "a/Tools/\351\207\215\345\220\257\345\205\254\344\274\227\345\217\267.md" "b/Tools/\351\207\215\345\220\257\345\205\254\344\274\227\345\217\267.md" deleted file mode 100644 index c1b55eb0..00000000 --- "a/Tools/\351\207\215\345\220\257\345\205\254\344\274\227\345\217\267.md" +++ /dev/null @@ -1,174 +0,0 @@ -# 前言 -2017年申请的**微信公众号**,没想到已经时隔**三年**了。那时候本科大四保上研究生,算是有比较多的时间可以维护微信公众号,其中公众号的内容是分享一些日常办公效率较高的应用软件。但,好景不长,直到2018年暑假开始了研究生生涯。 ->当然是内心充满期待和无尽的喜悦呀... - - -![就这?](https://imgkr.cn-bj.ufileos.com/6f79cef5-0009-4a45-ae52-91310effcbaa.jpg) - -# 研究生 -> 可能是我太菜了,时间久了,也成混子一个了。 - -## 探索中... -研一的课程,对于我来讲,可能太耗时间了,虽然我是**半路杀出来的程咬金**... -![还行,程咬金也没张飞吓人](https://imgkr.cn-bj.ufileos.com/0aed0d51-844f-4c11-8bb6-ba8495429a5f.jpg) -但,你想啊,貌似不是我一个咬金吧?好像还有很多半路杀出来的**张飞**呢! - -> 其实,我早就想跨入这一行当了,想体验一波,因此像研一这么多课,一把辛酸呀,只能舍弃一丢丢了。有些课在我以后找工作的过程当中,好像起到的作用并不是特别大,你们说呢?哈哈哈哈哈... - -想听接下来的故事,我们相约在**xxx**听我道来。 - -### 西藏xxx项目... -也没好过几个日子,实验室突然就让做了个项目撒。这个项目就分为两个大部分: - -1. **数据采集和渲染** -2. **数据分析和渲染** - -> 我个人原因,暂时放弃了机器学习、深度学习等人工智能领域。所以,我选择了数据采集和渲染。当然,也有很大程度上是和我大四前半年学习Python做网络爬虫项目以及大四下半年做的毕设有关系 - -#### 小插曲(网络爬虫) -说起这个事啊,当然保研过后,没什么大事要干,因此制定了计划学习一门特别容易上手的编程语言,当然觉得python挺火的,就选择了它,也没多想。为了激发自己的兴趣,先盘个网站的数据玩玩呗。 - -![当然没想那么多...](https://imgkr.cn-bj.ufileos.com/5c0a7286-78f4-42ea-8e13-852fbc9fe11a.jpg) - -> 当然,最后我也没进去哈,倘若进去了,难不成我在小黑屋里偷表情包嘛? - -首当其中,拿我们学校的网站做了测试玩玩。其主要的功能: - -1. **充宿舍电费**(学校web网站真的挺繁琐的...) -2. 因为第一个功能,又增加了**查看宿舍电费** -3. 熟悉的**登录管理**等。 -4. 界面-->PyQt5和微信小程序(由于时间问题...) - -**想和我聊细节,可以找我哦...** - -#### 小插曲(毕设web项目) -这个项目,其实主要的技术栈还是**Python**,并结合了Python的web框架**Flask**,扯进来了**深度学习的BP神经网络算法**,很粗略的那种。毕竟是web,所以当时粗略学习了Vue。你们懂得,当然这个前端还挺火,也易学。。 - - -![表演不了什么呀](https://imgkr.cn-bj.ufileos.com/861a1306-d9b6-4fd7-9165-2126a07a0b87.jpg) - -其实,也是因为xxx的给的课题,做个带算法的搜索引擎。。所以其功能: - -1. **搜索引擎**(被迫整了Elasticsearch,没想到现在都这么火...) -2. **数据采集**(那肯定是Python的scrapy分布式框架) -3. **数据存储**(当时用的MongoDB,其实搭配redis做分布式也挺好用) -4. **个性化推荐算法**(很粗略,BP神经网络) -5. **Web**(时间紧迫,还要写论文,因此选择易学的Python的Flask,没整Django) -6. **前端**(跟5一样的原因,选择了Vue+ElementUI组件) - -**想和我聊细节,可以找我哦...** - -插曲结束啦,当然,也因此暂时有了两篇软著...害! - ------- - -言归正传... - -选择了自己暂时擅长的框架。于是选择了**西藏xxx项目**的**数据采集**,就被我愉快的没花多少时间给做完了... - - -![就像工具人一样](https://imgkr.cn-bj.ufileos.com/9ff64576-2212-4304-8f40-387dedd97df0.jpg) - -### 痛定思痛 - -时间就到了2019年4月份了,种种无法言语的话,你们就当马赛克,不能浪费自己的时间,开始买Java书籍,又开始快乐的玩一波了,心累。虽然我知道肯定不能实习,放下了紧张刺激的步伐。 - -> 给个链接看一波,目前我看的书籍吧![https://www.processon.com/view/link/5e455cbae4b06b291a6e1af0](https://www.processon.com/view/link/5e455cbae4b06b291a6e1af0) - -给个小图看看吧-->: - - -![部分书籍](https://imgkr.cn-bj.ufileos.com/2e67fcf5-2d4a-4790-8802-f0a0b82df642.png) - -#### 小插曲(云) -2019年3月份以学生价买了阿里云,想搭建个博客玩玩,反正也不贵的嘛,100多来块。这100多来块确实让你学习到不少的linux知识,不过最终目的想走个过程... 当然,毫不犹豫的买了个域名,只要不是com和cn,其他还是挺便宜的,敢于下血本。。。虽然Dreamcats很穷。 -继续忧伤一会... - -> 博客地址:[http://dreamcat.ink/](http://dreamcat.ink/) - -#### 小插曲(竞赛) -2019年5月份实验室然俺们做了一个关于多说话人识别(深度学习)的竞赛。害! - -> 说实话,我的内心有点小拒绝的,毕竟我还想泡在看书的海洋当中。 - - -![假装快乐](https://imgkr.cn-bj.ufileos.com/03e8b03e-cfbe-41c4-affc-c1fc433f4261.jpg) - -不过,Dreamcats有点愧疚自己的队友了,确实没干啥,要算法,没算法,要编程,没编程,要写文档,没写文档,但是。我也是有尊严的,要画图,我还是乖乖的画图了。最后,当了个混子,抱了队友的一波大腿拿了个奖。挺不好意思嘚. 你们说呢 - -#### 小插曲(思考开题) - -Dreamcats这个人安排计划死死的,6月份就开始思考开题了。生活所迫,有异想天开的意思了,还是选择了深度学习来开题。方向:**音乐源分离**,想和我交流的,也可以找我。 - -由于实验室没有好的GPU,Dreamcats破费了,整了个二手稍微拿的出的1070显卡训练任务。 - -### 噩梦暑假 -首先,Dreamcats暑假没回家呀,好男儿,志在四方,扯远了哈,谁说不想家呢。其次,为了让自己的毕设容易点,7,8月起早贪黑的做实验,这段细节我就不仔细描述了,辛酸史。最后,再次印证了我不适合算法岗。 - -> 其实在训练的时候,也在码Java代码,看Java相关书籍。不信你去看[https://github.com/DreamCats/JavaBooks](https://github.com/DreamCats/JavaBooks) - -所以,辛酸史,就不提了,过去了。 - -### 噩梦论文 -你说,你数据有了,不得写论文嘛,至少到2020年5月份,一直被论文给困扰。 -- 上来就投三区.... -- 写response... -- 投四区.. -- 看淡了. - -结论:怪就怪自己的嘴,吹牛皮吹的过了。 - -#### 小插曲(皮肤病项目) -说起这个项目,Dreamcats是项目负责人,其实还是老样子,主要是深度学习,我是极度的不愿意,么办法,生活所迫。做的最后我都没法在简历上写了。你们懂得... - -> 我最后还真没写,只能说是不合适,再且没亮点。 - - -### 为了自己 -时间已经悄悄的来到了2020年,我拿什么xxx去秋招呢。好气~~~ - -![好气](https://imgkr.cn-bj.ufileos.com/c311342f-f8c7-4ecc-915e-ab6cde2148ec.jpg) - -#### 灵感 -关于我要找什么岗位,我就不说了,那么我要丰富自己的简历,像以上的四个项目是不合适的,前两个是我一个人全做的,第二个毕设相关用到了Elasticsearch这个亮点,太少了,拿不出手的。当然我买了票去了趟郊区的校区,俺想了想,嗯, 整个班车预约平台吧。起初就是为了集成一些比较火的中间件。其次学习一下当前经常碰见的业务是如何处理的。最后,蠢蠢欲动,撸代码。 - -> 认识了一位前端老友,和她一起做了这个项目。项目地址:[https://github.com/DreamCats/school-bus](https://github.com/DreamCats/school-bus) - -也谈不上多高端,项目所用的技术,我就不提了,有兴趣的可以去访问链接哦。有问题,**记得来找我** - -#### 总结吐血系列 -2020年,我的知识点很散,和乱,很多点,没有串在一起,于是乎,我就写了一套吐血系列。 -链接地址都在Github上。这里就不贴了。 - -#### 总结面经(持续) -找工作前,那还是要看一些的,我目前也就总结一点,由于种种以上提到的噩梦。耗费了时间,所以,接下来还是要总结的。其链接也在Github上。 - -#### 总结专题 -这里,可能对于某个知识点较为深入了。有兴趣的,也在Github上。 - -#### 刷题 -Leetcode和牛客一直整前100后者200就行了。时间充足,你就全整,开个玩笑。 - -#### 反复看 -个人感觉,有些你常用的,你忘得慢,不常用的知识点,忘的挺快的。我有过这种感受,但一定: -- **多写** -- **多看(知识和源码)** -- **多讲(我个人比较喜欢说话,各种讲给别人听)** - -这样感觉会好一些,忘得慢。面试造火箭嘛。 - -#### 努力,那还是试一下的。 -人嘛,总得要努力一下的嘛,要想获得自己想要的东西,不得努力一下试试呗?毕竟咱们都是普通老百姓嘛。对嘛。**更多的鸡汤,可以随时来找我,我都可以给你聊。不要钱。** - -> 知识点,在这里看体验更加:[https://dreamcater.gitee.io/javabooks/#/](https://dreamcater.gitee.io/javabooks/#/) - -### 重开公众号 -这一篇软文,不就是证明,我开始重新维护了嘛,毕竟目前还是有点小时间的,但是6月份-10月份应该还是有时间的。哈哈哈,不多说了,想和我聊,带着**酒**就行。有故事哦(在此滑稽...) - -![假装一下](https://imgkr.cn-bj.ufileos.com/57bb4b6d-a987-4695-8e87-0628d7fc2db7.gif) - -**文章字数不多,大家伙很多就能阅读完毕。** - - - - diff --git a/backup.md b/backup.md deleted file mode 100644 index 5e72dd7b..00000000 --- a/backup.md +++ /dev/null @@ -1,335 +0,0 @@ -# JavaBooks -## 引言 -> 总结的知识点包括: -> -> - Java基础 -> - Java集合 -> - Java多线程 -> - JVM虚拟机 -> - Spring系列(SpringIOC、SpringAOP、SpringMVC及SpringBoot) -> - ORM(Mybatis) -> - 数据库(Mysql和Redis) -> - 微服务(Dubbo) -> - 消息队列(RocketMQ) -> - 计算机网络 -> - 操作系统(Linux系统) -> - 微服务项目(SpringBoot+Mybatis+Dubbo+Mysql+Redis+RocketMQ) -> - 数据结构与算法(Leetcode/剑指Offer) -> - 设计模式 -> - 常用工具 -> -> **如果观看不流畅,可以去我的博客浏览面试知识点** -> - [dreamcat.ink](http://dreamcat.ink/) -> - [在线阅读](https://dreamcater.gitee.io/javabooks/) -> - **Dreamcats的公众号**:[访问链接](https://mp.weixin.qq.com/s/NTRnfdPcr2pVnTvhFMYJCg) - -## 常用网站 -- [:bookmark:开源项目总结](/Tools/network/开源的github项目总结.md) -- [:fire:常用的在线网站](/Tools/network/收集常用的网站(持续更新...).md) -- [:sparkles:emoji集合](/Tools/network/github表情图标.md) -- [:smiling_imp:Linux命令行的奇淫技巧](/Tools/network/Linux命令行的奇淫技巧.md) -- [📖今日热榜](https://tophub.today/):一款新闻聚合的产品,个人感觉还不错,闲时可以看一下新闻,可选择订阅哦 - -## Java面试思维导图(包括分布式架构) -- [总体架构](https://www.processon.com/view/link/5e170217e4b0bcfb733ce553) **这边就不放图了,放图的字体小,放大可能模糊。该图还在持续总结中...** -- [Java常见基础问题](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) **常见的基础问题,这是必须要掌握。** -- [Java常见集合问题]() **还没总结,后续总结...** -- [Java常见多线程问题](https://www.processon.com/view/link/5e4ab92de4b0996b2ba505bf) **常见的多线程问题,也是必须掌握...** -- [JVM常见问题](https://www.processon.com/view/link/5e4c0704e4b00aefb7e74f44) **常见的JVM要掌握的点...** -- [Spring常见问题](https://www.processon.com/view/link/5e846de9e4b07b16dcdb63f0) **常见的Spring面试的问题...** -- [Mybatis常见问题](https://www.processon.com/view/link/5e4e3b7ae4b0369b916b2e71) **常见的Mybatis面试的问题...** -- [MySQL常见问题](https://www.processon.com/view/link/5e9b0cb15653bb1a686e17ea) **常见的MySQL面试的问题...** -- [Redis常见问题](https://www.processon.com/view/link/5ea2da5907912948b0d89a0a) **常见的Redis面试的问题...** -- [计算机网络常见问题](https://www.processon.com/view/link/5eb8c93be401fd16f42b5f77) **常见的计算机网络面试的问题...** -- [Dubbo常见问题](https://www.processon.com/view/link/5eb8c9715653bb6f2aff7c11) **常见的Dubbo的问题...** -- [RocketMQ常见问题](https://www.processon.com/view/link/5ecf208f7d9c08156c6c37e3) **常见的RocketMQ的问题...** - -## 微服务班车在线预约系统 -- [微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 -- [BUG排查之一](https://github.com/DreamCats/school-bus/blob/master/doc/%E4%B8%8A%E7%BA%BF%E9%81%87%E5%88%B0%E7%9A%84bug.md) - -## 个人事迹 - -**本来是不想贴的,之前做了图,见丑了...** - -- [本科事迹](https://www.processon.com/view/link/5e45532fe4b06b291a6e0d7c) **不是科班出身,专业是就不说啦...** -- [研究生事迹](https://www.processon.com/view/link/5e455cbae4b06b291a6e1af0) **还未结束,后续继续补充...** -- [个人博客](http://www.dreamcat.ink) **这是我个人博客,该项目的文章都在博客上有体现,github上若是阅读不佳,可以去博客上观看,时间紧迫,有些地方还没有细细的优化** - -## 吐血系列 -- **个人吐血系列-总结Java基础**: [本地阅读](/Interview/crazy/个人吐血系列-总结Java基础.md)->[博客阅读](http://dreamcat.ink/2020/03/27/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-chu/)-> [掘金阅读](https://juejin.im/post/5e7e0615f265da795568754b) -- **个人吐血系列-总结Java集合**: [本地阅读](/Interview/crazy/个人吐血系列-总结Java集合.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-java-ji-he/)-> [掘金阅读](https://juejin.im/post/5e801e29e51d45470b4fce1c) -- **个人吐血系列-总结Java多线程**: [本地阅读](/Interview/crazy/个人吐血系列-总结Java多线程.md)->[博客阅读](http://dreamcat.ink/2020/03/25/ge-ren-tu-xie-xi-lie-zong-jie-java-duo-xian-cheng/)-> [掘金阅读-1](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) [掘金阅读-2](https://juejin.im/post/5e7e10b5518825739b2d1fb1) -- **个人吐血系列-总结JVM**: [本地阅读](/Interview/crazy/个人吐血系列-总结JVM.md)->[博客阅读](http://dreamcat.ink/2020/03/28/ge-ren-tu-xie-xi-lie-zong-jie-jvm/)-> [掘金阅读](https://juejin.im/post/5e8344486fb9a03c786ef885) -- **个人吐血系列-总结Spring**: [本地阅读](/Interview/crazy/个人吐血系列-总结Spring.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-spring/)-> [掘金阅读](https://juejin.im/post/5e846a4a6fb9a03c42378bc1) -- **个人吐血系列-总结Mybatis**: [本地阅读](/Interview/crazy/个人吐血系列-总结Mybatis.md)->[博客阅读](http://dreamcat.ink/2020/03/29/ge-ren-tu-xie-xi-lie-zong-jie-mybatis/)-> [掘金阅读](https://juejin.im/post/5e889b196fb9a03c875c8f50) -- **个人吐血系列-总结MySQL**: [本地阅读](/Interview/crazy/个人吐血系列-总结MySQL.md)->[博客阅读](http://dreamcat.ink/2020/03/30/ge-ren-tu-xie-xi-lie-zong-jie-mysql/)-> [掘金阅读](https://juejin.im/post/5e94116551882573b86f970f) -- **个人吐血系列-总结Redis**: [本地阅读](/Interview/crazy/个人吐血系列-总结Redis.md)->[博客阅读](http://dreamcat.ink/2020/03/31/ge-ren-tu-xie-xi-lie-zong-jie-redis/)-> [掘金阅读](https://juejin.im/post/5e9d6a9ff265da47e34c0e8a) -- **个人吐血系列-总结计算机网络**: [本地阅读](/Interview/crazy/个人吐血系列-总结计算机网络.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)-> [掘金阅读](https://juejin.im/post/5ea383c251882573716ab496) -- **个人吐血系列-Dubbo**: [本地阅读](/Interview/crazy/个人吐血系列-总结Dubbo.md)->[博客阅读](http://dreamcat.ink/2020/04/02/ge-ren-tu-xie-xi-lie-zong-jie-ji-suan-ji-wang-luo/)-> [掘金阅读](https://juejin.im/post/5eb11127f265da7bb46bce26) -- **个人吐血系列-RocketMQ**: [本地阅读](/Interview/crazy/个人吐血系列-总结RocketMQ.md)->[博客阅读](http://dreamcat.ink/2020/04/01/ge-ren-tu-xie-xi-lie-zong-jie-rocketmq/)-> [掘金阅读](https://juejin.im/post/5ecf1f716fb9a047f338b972) - -## 面经汇总 -### 美团 -- [美团-1](/Interview/mianjing/meituan/meituan01.md) -- [美团-2](/Interview/mianjing/meituan/meituan02.md) -- [美团-3](/Interview/mianjing/meituan/meituan03.md) -- [美团-4](/Interview/mianjing/meituan/meituan04.md) -- [美团-5](/Interview/mianjing/meituan/meituan05.md) - -- [美团所有问题汇总](/Interview/mianjing/meituan/美团所有问题汇总.md) - -### 京东 -- [京东-1](/Interview/mianjing/jingdong/jingdong01.md) -- [京东-2](/Interview/mianjing/jingdong/jingdong02.md) -- [京东-3](/Interview/mianjing/jingdong/jingdong03.md) -- [京东-4](/Interview/mianjing/jingdong/jingdong04.md) -- [京东-5](/Interview/mianjing/jingdong/jingdong05.md) -- [京东所有问题汇总](/Interview/mianjing/jingdong/京东所有问题汇总.md) - -### 拼多多 -- [拼多多所有问题汇总](/Interview/mianjing/pinxx/拼多多所有问题汇总.md) - -## 刷题系列 -- [SwordOffer-书的顺序](/SwordOffer/README.md) -- [SwordOffer-牛客网的顺序](https://github.com/DreamCats/LeetNiu#%E5%89%91%E6%8C%87offer-%E7%BD%91%E7%AB%99) -- [LeetCode-正常顺序](/LeetCode/README.md) -- [链表-专题](/LeetCode/数据结构-链表.md) -- [树-专题](/LeetCode/数据结构-树-基础.md) -- [栈和队列-专题](/LeetCode/数据结构-栈和队列.md) -- [双指针-专题](/LeetCode/数据结构-双指针.md) -- [哈希表-专题](/LeetCode/数据结构-哈希表.md) -- [位运算-专题](/LeetCode/数据结构-位运算.md) -- [动态规划-专题](/LeetCode/数据结构-动态规划.md) -- [排序-专题](/LeetCode/数据结构-排序.md) -- [查找-专题](/LeetCode/数据结构-二分法.md) -- [剑指offer-带注释](/SwordOffer/剑指offer.md) - - -## 基础 -- [Java面试基础一些常见问题-思维导图](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) -- [Java面试基础知识](/Basics/Java面试基础知识.md) -- [Java面试基础知识](/Basics/Java面试基础常见问题.md) - -### 基础图解 -- [异常分类图解](https://www.processon.com/view/link/5e404235e4b021dc289fbf72) - -## 集合源码 -- [Java面经-Java集合框架](/Collections/Java面经-Java集合框架.md) -- [Java面经-ArrayList源码解析](/Collections/Java面经-ArrayList源码解析.md) -- [Java面经-LinkedList源码解析](/Collections/Java面经-LinkedList源码解析.md) -- [Java面经-HashSet-HashMap源码解析](/Collections/Java面经-HashSet-HashMap源码解析.md) -- [Java面经-LinkedHashSet-Map源码解析](/Collections/Java面经-LinkedHashSet-Map源码解析.md) -- [Java面经-TreeSet-TreeMap源码解析](/Collections/Java面经-TreeSet-TreeMap源码解析.md) -- [Java面经-PriorityQueue源码解析](/Collections/Java面经-PriorityQueue源码解析.md) -- [Java面经-Stack-Queue源码解析](/Collections/Java面经-Stack-Queue源码解析.md) -- [HashMap-ConcurrentHashMap面试必问](/Collections/HashMap-ConcurrentHashMap面试必问.md) - -### 集合图解 -- [ArrayList源码分析](https://www.processon.com/view/link/5e13ddf5e4b07ae2d01c7369) -- [LinkedList源码分析](https://www.processon.com/view/link/5e13e641e4b0c090e0b88a59) -- [HashMap源码分析](https://www.processon.com/view/link/5e159150e4b07db4cfb0f418) - -## 多线程系列 -- [多线程思维导图](https://www.processon.com/view/link/5e4ab92de4b0996b2ba505bf) -- [Java多线程-并发基础常见面试题总结](/Multithread/Java多线程-并发基础常见面试题总结.md) -- [Java多线程-Synchronized](/Multithread/Java多线程-synchronized.md) -- [Java多线程-volatile](/Multithread/深刻理解volatile的一切.md) -- [Java多线程-CAS](/Multithread/CAS底层解析.md) -- [Java多线程-ThreadLocal](/Multithread/Java多线程-ThreadLocal.md) -- [Java多线程-Atomic原子类](/Multithread/Java多线程-Atomic原子类.md) -- [Java多线程-AQS](/Multithread/Java多线程-AQS.md) -- [Java多线程-线程池](/Multithread/Java多线程-线程池.md) -- [Java多线程-并发进阶常见面试题总结](/Multithread/Java多线程-并发进阶常见面试题总结.md) -- [多线程一些例子](/Multithread/README.md) -- [Java多线程常见问题](/Multithread/Java多线程常见问题.md) - -### 多线程图解 -- [谈谈Java内存模型](https://www.processon.com/view/link/5e129d57e4b0da16bb11d127) -- [有个成员变量int a = 1,那么a和1分别在jvm哪里](https://www.processon.com/view/link/5e13500de4b009af4a5fc40b) -- [线程的状态周期图](https://www.processon.com/view/link/5e16a379e4b0f5a7ed06d2fb) -- [volatile保证内存可见性和避免重排](https://www.processon.com/view/link/5e12e591e4b061a80c683639) -- [volatile不能保证原子性操作](https://www.processon.com/view/link/5e130e51e4b07db4cfac9d2c) -- [无锁-偏向锁-轻量级锁-重量级锁](https://www.processon.com/view/link/5e1744a7e4b0f5a7ed086f4a) -- [内存屏障](https://www.processon.com/view/link/5e4420bde4b06b291a6c463b) - -## JVM -- [JVM面试思维导图](https://www.processon.com/view/link/5e4c0704e4b00aefb7e74f44) -- [JVM-类文件结构](/Jvm/JVM-类文件结构.md) -- [JVM-类加载过程](/Jvm/JVM-类加载过程.md) -- [JVM-类加载机制](/Jvm/Java面经-类加载机制.md) -- [JVM-类加载器](/Jvm/JVM-类加载器.md) -- [JVM-内存模型](/Jvm/Java面经-内存模型.md) -- [JVM-对象创建](/Jvm/JVM-对象创建.md) -- [JVM-垃圾回收](/Jvm/JVM-垃圾回收.md) -- [JVM-调优参数](/Jvm/Java面经-JVM调优参数.md) -- [JVM面试常见问题](/Jvm/JVM面试常见问题.md) - -### JVM例子 -- [引用计数的循环引用问题](/Jvm/src/T2.java) -### JVM图解 -- [JVM整个流程](https://www.processon.com/view/link/5e1182afe4b009af4a5cc54d) - -## Spring系列 -### 参考笔记 -- [b站视频入口](https://www.bilibili.com/video/av32102436?p=1) -- [代码资料](https://gitee.com/adanzzz/spring_source_parsing_data) -- [Spring注解驱动开发](https://www.processon.com/view/link/5e30213ae4b096de64c8e9bf) - -### 源码总结 -- [Spring和SpringAOP](/Spring和SpringAOP源码总结.md) -- [参考这位大佬的MVC原理](https://snailclimb.gitee.io/javaguide/#/docs/system-design/framework/spring/SpringMVC-Principle) **这位大佬总结的不错,可参考** -- [SpringMVC开发文档](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html) **这里就不贴视频中的SpringMVC工程** - -### 手写简单的IOC和AOP -- [手写简单的IOC](/spring-ioc) 非常简单,每行都有注释 -- [手写简单的AOP](/spring-aop2) 非常简单,每行都有注 - -### SpringBoot -**[参考这位大佬](https://snailclimb.gitee.io/springboot-guide/#/)** -**项目结构过于具体简单的文件解释就不说了,主要是看细节和原理** -- [SpringBoot启动流程分析](/SpringBoot启动流程分析.md) - -### 常见问题 -- [Spring面试常见问题](/Interview/spring/Spring面试常见问题.md) - -## MyBatis系列 -- [MyBatis常见问题的思维导图](https://www.processon.com/view/link/5e4e3b7ae4b0369b916b2e71) -- [MyBatis面试相关系列](https://github.com/DreamCats/SpringBooks#mybatis) -- [MyBatis源码](https://juejin.im/entry/5b9886735188255c960c1bec) -- [MyBatis面试常见问题](/Interview/mybatis/MyBatis面试常见问题.md) - -## 计算机网络 -- [计算机网络原理-DNS是干什么的?](/Interview/network/计算机网络原理-DNS是干什么的.md) -- [计算机网络原理-http那些事儿](/Interview/network/计算机网络原理-http那些事儿.md) -- [动画:用动画给面试官解释 TCP 三次握手过程](https://blog.csdn.net/qq_36903042/article/details/102513465) -- [动画:用动画给女朋友讲解 TCP 四次分手过程](https://blog.csdn.net/qq_36903042/article/details/102656641) -- [计算机网络面试常见问题](Interview/network/计算机网络面试常见问题.md) - -## 数据库 -- [SQL-数据库系统原理](/Interview/mysql/sql-数据库系统原理.md) -- [MySQL中ACID的原理](/Interview/mysql/Mysql中ACID的原理.md) -- [SQL-存储引擎](/Interview/mysql/sql-存储引擎.md) -- [SQL-索引(B+树)](/Interview/mysql/sql-索引-B-树.md) -- [MySQL面试常见问题](Interview/mysql/MySQL面试常见问题.md) - -### Redis -- [Redis-面试常见的问题](/Interview/redis/Redis-面试常见的问题.md) - -## 分布式 -- [Dubbo-面试常见问题](/Interview/crazy/个人吐血系列-总结Dubbo.md) -- [消息队列-RocketMQ面试常见问题](Interview/crazy/个人吐血系列-总结RocketMQ.md) - -## Linux -- [linux-基础](/Interview/linux/linux-基础.md) - - -## 手撕代码系列 -- [equals和hashcode](/Interview/codes/basics/equals和hashcode.md) -- [类型转换的问题](/Interview/codes/basics/类型转换的问题.md) -- [String的一些问题](/Interview/codes/basics/String的一些问题.md) -- [面向对象的一些问题](/Interview/codes/basics/面向对象的一些问题.md) -- [常见关键字问题](/Interview/codes/basics/常见关键字问题.md) -- [IO的一些问题](/Interview/codes/basics/IO的一些问题.md) -- [Java值传递的问题](/Interview/codes/basics/Java值传递的问题.md) -- [浅拷贝和深拷贝例子](/Interview/codes/basics/浅拷贝和深拷贝例子.md) -- [异常的一些问题](/Interview/codes/basics/异常的一些问题.md) -- [反射的一些问题](/Interview/codes/basics/反射的一些问题.md) -- [集合总览](/Interview/codes/collection/集合总览.md) -- [ArrayList的一些问题](/Interview/codes/collection/ArrayList的一些问题.md) -- [LinkedList的一些问题](/Interview/codes/collection/LinkedList的一些问题.md) -- [HashMap的一些问题](/Interview/codes/collection/HashMap的一些问题.md) -- [ConcurrentHashMap的一些问题](/Interview/codes/collection/ConcurrentHashMap的一些问题.md) -- [手写LRU算法](/Interview/codes/collection/手写LRU算法.md) -- [BlockingQueue的一些问题](/Interview/codes/collection/BlockingQueue的一些问题.md) -- [线程生命周期一些问题](/Interview/codes/thread/线程生命周期一些问题.md) -- [线程的启动方式](/Interview/codes/thread/线程的启动方式.md) -- [Runnable、Callable和FutureTask](/Interview/codes/thread/Runnable、Callable和FutureTask.md) -- [举个多线程的小例子](/Interview/codes/thread/举个多线程的小例子.md) -- [主线程等待多个子线程的例子](/Interview/codes/thread/主线程等待多个子线程的例子.md) -- [死锁例子](/Interview/codes/thread/死锁例子.md) -- [volatile的一些问题](/Interview/codes/thread/volatile的一些问题.md) -- [synchronized的一些问题](/Interview/codes/thread/synchronized的一些问题.md) -- [CAS的一些问题](/Interview/codes/thread/CAS的一些问题.md) -- [ThreadLocal的一些问题](/Interview/codes/thread/ThreadLocal的一些问题.md) -- [AQS的一些问题](/Interview/codes/thread/AQS的一些问题.md) -- [生产者和消费者的三种例子](/Interview/codes/thread/生产者和消费者的三种例子.md) -- [线程池的一些问题](/Interview/codes/thread/线程池的一些问题.md) -- [Java锁的介绍](/Interview/codes/thread/Java锁的介绍.md) -- [循环引用的小例子](/Interview/codes/jvm/循环引用的小例子.md) -- [栈溢出的小例子](/Interview/codes/jvm/栈溢出的小例子.md) -- [堆溢出的小例子](/Interview/codes/jvm/堆溢出的小例子.md) -- [查看反编译源码](/Interview/codes/jvm/查看反编译源码.md) -- [查看JVM吐血系列](/Interview/crazy/个人吐血系列-总结JVM.md) -- [查看Spring吐血系列](/Interview/crazy/个人吐血系列-总结Spring.md) -- [手写SpringIoc容器](/Interview/codes/spring/手写SpringIoc容器.md) -- [手写SpringAOP代码](/Interview/codes/spring/手写SpringAOP代码.md) -- [MySQL是如何执行一条SQL语句的](/Interview/codes/mysql/MySQL是如何执行一条SQL语句的.md) -- [InnoDB和MyISAM](/Interview/codes/mysql/InnoDB和MyISAM.md) -- [红黑树、B树和B+树](/Interview/codes/mysql/红黑树、B树和B+树.md) -- [MySQL中索引的保存机制与B- B+树](https://blog.csdn.net/TaoTaoFu/article/details/69376320) -- [MySQL索引原理](https://juejin.im/post/5a6873fbf265da3e393a97fa) -- [索引种类](/Interview/codes/mysql/索引种类.md) -- [MySQL的ACID原理](/Interview/codes/mysql/MySQL的ACID原理.md) -- [并发事务带来的问题](/Interview/codes/mysql/并发事务带来的问题.md) -- [数据库范式](/Interview/codes/mysql/数据库范式.md) -- [数据库结构优化](/Interview/codes/mysql/数据库结构优化.md) -- [MVCC的缺点](/Interview/codes/mysql/MVCC的缺点.md) -- [MySQL读写分离主从复制原理](/Interview/codes/mysql/MySQL读写分离主从复制原理.md) -- [查看Redis吐血系列](/Interview/crazy/个人吐血系列-总结Redis.md) -- [深究Redis的底层结构](/Interview/codes/redis/深究Redis的底层结构.md) -- [Redis做延迟队列](/Interview/codes/redis/Redis做延迟队列.md) -- [查看计算机网络吐血系列](/Interview/crazy/个人吐血系列-总结计算机网络.md) -- [查看Dubbo吐血系列](/Interview/crazy/个人吐血系列-总结Dubbo.md) -- [Dubbo的负载均衡](http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html) -- [查看RocketMQ吐血系列](/Interview/crazy/个人吐血系列-总结RocketMQ.md) -- [9种分布式唯一ID](https://zhuanlan.zhihu.com/p/107939861) -- [分布式事务](https://juejin.im/post/5b5a0bf9f265da0f6523913b) -- [不会看Explain执行计划,劝你简历别写熟悉SQL优化](https://juejin.im/post/5ec4e4a5e51d45786973b357) -- [几种分布式锁的实现方式](https://juejin.im/post/5cff593c6fb9a07ec56e6ed4) -- [七张图彻底讲清楚ZooKeeper分布式锁的实现原理](https://juejin.im/post/5c01532ef265da61362232ed) -- [基于Redis实现的分布式锁](https://juejin.im/post/5cc165816fb9a03202221dd5) -- [Sentinel相关概念](/Interview/codes/other/Sentinel相关概念.md) -- [分布式事务的相关概念](https://www.infoq.cn/article/g1avP9FUA6CDOYRAlv4R) -- [RocketMQ做延迟队列](https://blog.csdn.net/itanping/article/details/101070156) -- [5分钟搞懂布隆过滤器,亿级数据过滤算法你值得拥有!](https://juejin.im/post/5de1e37c5188256e8e43adfc) - - -### 项目 -- 项目地址:[微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) - -- [环境搭建文档](/Interview/codes/bus/环境搭建文档.md) -- [Redis绑定Token分析文档](/Interview/codes/bus/Redis绑定Token.md) -- [用户服务所有接口分析文档](/Interview/codes/bus/用户服务.md) -- [班车服务所有接口分析文档](/Interview/codes/bus/班车服务.md) -- [订单服务所有接口分析文档](/Interview/codes/bus/订单服务.md) -- [支付服务所有接口分析文档](/Interview/codes/bus/支付服务.md) -- [添加订单、支付和退款的业务结合消息队列](/Interview/codes/bus/RocketMQ最终一致性.md) -- [Redis的key过期事件结合自动取消订单业务](/Interview/codes/bus/Redis的key过期事件.md) -- [SQL语句调优](/Interview/codes/bus/业务逻辑SQL语句.md) -- [Zookeeper的bug之一](/Interview/codes/bus/上线遇到的bug.md) - -### 设计模式 -- [单例模式](/Interview/codes/modes/单例模式.md) -- [工厂模式](/Interview/codes/modes/工厂模式.md) -- [代理模式](/Interview/codes/modes/代理模式.md) -- [模版方法模式](/Interview/codes/modes/模板方法模式.md) -- [观察者模式](/Interview/codes/modes/观察者模式.md) -- [装饰器模式](/Interview/codes/modes/装饰器模式.md) - -### 其他 -- [二叉树的深度搜索遍历](/Interview/src/DFSDemo.java) - -## 微信公众号推荐 - -- **JavaGuide**:大家都懂的,帮这位老哥宣传一下-->"开源项目—JavaGuide (56k+Star)作者运营维护。专注Java后端学习!内容涵盖Java面试指南、Spring Boot、Dubbo、Zookeeper、Redis、Nginx、消息队列、系统设计、架构、编程规范等内容。" -- **程序员乔戈里**:我也经常浏览,大佬也非常勤奋,也宣传一下-->"开源项目—JavaGuide (56k+Star)作者运营维护。专注Java后端学习!内容涵盖Java面试指南、Spring Boot、Dubbo、Zookeeper、Redis、Nginx、消息队列、系统设计、架构、编程规范等内容。" -- **帅地玩编程**:少不了**帅地呀**,hhh-->"本号专注于讲解数据结构与算法、计算机基础(如计算机网络+操作系统+数据库+Linux)等编程知识,期待你的关注。" -- **GitHubDaily**:经常分享Github一些项目-->"专注于分享 Python、Java、Web、AI、数据分析等多个领域的优质学习资源、开源项目及开发者工具。" -- **方志朋**:号主为BAT一线架构师,CSDN博客专家,博客访问量突破一千万,著有畅销书《深入理解SpringCloud与微服务构建》。主要分享Java、Python等技术,用大厂程序员的视角来探讨技术进阶、面试指南、职业规划等。15W技术人的选择! -- **好好学java**:学习Java必备公众号,关注于Java、算法,公众号每日与您分享Java知识,定期的分享面试题,关注我们吧,和小海一起学习进步! -- **小鹿动画学编程**:和小鹿同学一起用动画的方式从基础学编程,将 Web前端领域、数据结构与算法、网络原理等通俗易懂的呈献给小伙伴。先定个小目标,原创 1000 篇的动画技术文章,和各位小伙伴一起学习! -- **Java3y**:看人家说的:”只有光头才能变强“,头像也是个光头呢。 - - - diff --git a/books.md b/books.md new file mode 100644 index 00000000..ba2b03c7 --- /dev/null +++ b/books.md @@ -0,0 +1,114 @@ +> 关于面试Java后端的岗位的我, 当时看了一些书籍。 + +### Head First Java + +![X1D9TJ-YCTMZC](https://gitee.com/dreamcater/blog-img/raw/master/uPic/X1D9TJ-YCTMZC.png) + +以某个xxx的故事开始讲解Java的前世今生,初学者看了拍手叫好。(还行,当时看的比较快) + + +### 深入分析JavaWeb + +![深入分析JavaWeb-NR7M1M](https://gitee.com/dreamcater/blog-img/raw/master/uPic/深入分析JavaWeb-NR7M1M.png) + +JavaWeb的一些概念,不是特别的深入。 + +### Spring实战 + +![Spring实战-DYVKDu](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Spring实战-DYVKDu.png) + +这本书讲Spring还是比较细的,如果没有做过基于Spring的项目,可能要看有点久哦。 + +### Springboot+Vue + +![Springboot+Vue-MIufIX](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Springboot+Vue-MIufIX.png) + +之前做过Vue+Flask,现在来试试Springboot+Vue,这本书讲的是一个微人事系统,github也开源的。 + + +### MySQL必知必会 + +![MySQL必知必会-zR7ROL](https://gitee.com/dreamcater/blog-img/raw/master/uPic/MySQL必知必会-zR7ROL.png) + +MySQL语句的使用,工具类的书。 + +### InnoDB技术内幕 + +![InnoDB技术内幕-HzlwEB](https://gitee.com/dreamcater/blog-img/raw/master/uPic/InnoDB技术内幕-HzlwEB.png) + +这本书还是要多品味品味的,讲Innodb引擎比较透彻。 + +### 漫画算法-小灰? + +![漫画算法-小灰-SGlznk](https://gitee.com/dreamcater/blog-img/raw/master/uPic/漫画算法-小灰-SGlznk.png) + +常见的算法,以漫画的形式讲解,还是挺有意思的, 但是代码有一些bug + +### 大话数据结构 + +![大话数据结构-WobOhO](https://gitee.com/dreamcater/blog-img/raw/master/uPic/大话数据结构-WobOhO.png) + +怪我那本黑色的神书,看的枯燥乏味,于是看起了这本书 + +### 图解HTTP + +![图解HTTP-pVaJQc](https://gitee.com/dreamcater/blog-img/raw/master/uPic/图解HTTP-pVaJQc.png) + +计算机网络还不是要掌握? + +### 图解TCP/IP + +![图解TCP:IP-vW7Gi4](https://gitee.com/dreamcater/blog-img/raw/master/uPic/图解TCP:IP-vW7Gi4.png) + +看了图解HTTP,能不看图解TCP? + +### 计算机网络自顶向下 + +![计算机网络自顶向下-RQFiZe](https://gitee.com/dreamcater/blog-img/raw/master/uPic/计算机网络自顶向下-RQFiZe.png) + +我特意又买了这本书,归纳总结一波,看看到底有什么不同,讲的很可真细,值得看 + +### Redis设计与实现 + +![Redis设计与实现-jv3YTO](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Redis设计与实现-jv3YTO.png) + +讲了很多Redis的原理,八股文被背? + +### Java核心卷1 + +![Java核心卷1-8Gie6l](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Java核心卷1-8Gie6l.png) + +话不多说,Java的工具书,适合查找已经忘记的API,hhh(官方文档还是要看的)。 + +### Java高并发编程之美 + +![Java高并发编程之美-4YRbUO](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Java高并发编程之美-4YRbUO.png) + +这本书前半部分,讲的挺简单的,后边讲源码,还是挺深入的,尤其是JUC。 + +### Java并发编程艺术 + +![Java并发编程艺术-hG2iwx](https://gitee.com/dreamcater/blog-img/raw/master/uPic/Java并发编程艺术-hG2iwx.png) + +我感觉看概念,看这本书最合适,而且面试问的很多东西,看这本书即可,想看源码,两本书结合都看,但是还要看JDK才行。 + +### 深入理解Java虚拟机 + +![深入理解Java虚拟机-kcvcPs](https://gitee.com/dreamcater/blog-img/raw/master/uPic/深入理解Java虚拟机-kcvcPs.png) + +八股文,不用多说了吧?(看N遍了) + +### 大型网站技术架构 + +![大型网站技术架构-3bCC5z](https://gitee.com/dreamcater/blog-img/raw/master/uPic/大型网站技术架构-3bCC5z.png) + +我对架构挺感兴趣的,这不,买了这本书品尝了一波,也没有品尝出来什么味道。 + + +### 剑指offer + + +![剑指offer-w4dlkC](https://gitee.com/dreamcater/blog-img/raw/master/uPic/剑指offer-w4dlkC.png) + +刷好几遍了,必看 + diff --git a/wx.md b/wx.md new file mode 100644 index 00000000..70a8b7ea --- /dev/null +++ b/wx.md @@ -0,0 +1,118 @@ +--- +title: 互联网面试题助手 +weight: 1 +--- + +# 互联网面试题助手 + +
+Dc-Notes-small-OlULPl +
+ +![](https://cdn.nlark.com/yuque/0/2021/png/1067743/1616314950564-0a745bb8-e526-41ba-a11f-b6a4c9c1241b.png#align=left&display=inline&height=2228&margin=%5Bobject%20Object%5D&originHeight=2228&originWidth=2658&size=0&status=done&style=none&width=2658) + +# 需求 + +> 秋招一般从三个方面准备: +> +> - 知识点:形成个人体系,要根据自己的简历所建立的知识点 +> - 算法:开发岗的话,一般都是 leetcode 的题,不需要盲目刷完,后面我会讲 +> - 项目:要有针对性的项目,而且要不断的思考项目的亮点、困难点和如何解决的,以及多个方案对比。 + +如上图,从功能分析,大概分为三个子功能,也就对应三个功能的主页面。 + +## 面经 + +据我个人秋招经历来讲,看面经是又必须要的,有两种好处: + +1. 秋招之前,不知道面试官问什么,也并不知道如何回答,如果提前看了一些面经上的问题,心中大概有一些眉目,可以私下多准备,这时候一方面可以提供自信心;另一方面,不容易紧张。 +1. 查漏补缺,如果有了个人知识体系,看面经可以不断的查看自己哪部分没考虑到,可以及时补缺。 + +接着,对这个功能,我首先是分为前后端,算法岗暂时不涉及。毕竟前后端的面经是不一样的。目前涉及的公司大概有 20-30 之间吧,后续可以补充,当然,常见的大厂肯定是有的。当然,🐂 客网有这些面经呀,但是有时候用手机看也挺方便的,虽然 🐂🐂 也有小程序,哈哈哈。但总体来讲,还是稍微复杂了一些,我仅仅是想一个简简单单的功能的嘛,简约。 + +## 算法 + +这一块分为笔试和面试。以我秋招的经历,我个人觉得,笔试常考的类型: + +1. 二分法 +2. 字符串 +3. dfs +4. bfs +5. 贪心 +6. 排序 +7. 哈希 +8. 规律题 +9. 动态规划 +10. 双指针 +11. 数学 +12. 模拟题 +13. 图 + +而,面试一般常考的类型: + +1. 链表 +2. 树(迭代和递归,可都要熟练) +3. 二分法 +4. dfs +5. bfs +6. 动态规划 +7. 背包 +8. 双指针 +9. 哈希 +10. 排序 +11. 数组 +12. 字符串 + +这个功能,有三大好处: + +1. 不同语言是如何解题(js、go 和 java) +2. 对着标题,尝试一下是否有思路 +3. 不断的手写,要熟能生巧 + +其实,我个人觉得,没有必要将 leetcode 全部刷完撒,而且面试不会考那些很陌生的题,一般都是常考的题,因为有价值撒。其实根据我的观察,常考的那些题,也就 300-400 道,而且一般都是考中等题,比较典型的 hard 题,可以做做,但没必要全做。 + +## 知识点 + +这一块,我秋招的面试岗位是 Java 服务端,所以总结了大量的关于 Java 的知识点,以及形成了一套 Java 生态的个人体系,当然也是根据我所设计的**班车项目**而来的。前不久,学了一下前端的知识,其实我也是想对比前端的 js 和 java 相同点和不同点,学习一下其中的思想。这一块,也是分为前后端,当然,我总结了 300 套面经的内容,并总结了面试题的频率,所以才有此针对做了一些知识点。前端包括: + +1. js +2. css +3. html +4. 浏览器 +5. vue +6. 计算机网络 + +当然,也得根据自身的简历哈。后端这一块: + +1. Java 基础 +2. Java 集合 +3. Java 多线程 +4. JVM +5. Spring +6. MySQL +7. Redis +8. 计算机网络 +9. 操作系统 +10. 分布式 + +当然,以上关于班车项目,我后续会总结。班车项目,目前存放在了 Github 上。链接:[https://github.com/DreamCats/Dc-Notes](https://github.com/DreamCats/Dc-Notes) + +# 页面 + +页面虽然比较简单,但功能实用,将面经、算法和知识点集合在一起方便背诵。 + +## 主页 + +![](https://imgs.heiye.site/byte/1644979793216.png) + +![](https://imgs.heiye.site/byte/1644979858216.png) + +![](https://imgs.heiye.site/byte/1644979883388.png) + +## 知识页面 + +![](https://imgs.heiye.site/byte/1644979824216.png) + +## 面经页面 + +![](https://imgs.heiye.site/byte/1644979841099.png)