diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..558bfba --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +.idea +/local.properties +.DS_Store +build/ +/captures +.externalNativeBuild +apk/ +gradle.properties diff --git a/.gradle/2.14.1/taskArtifacts/cache.properties b/.gradle/2.14.1/taskArtifacts/cache.properties deleted file mode 100644 index 21621ff..0000000 --- a/.gradle/2.14.1/taskArtifacts/cache.properties +++ /dev/null @@ -1 +0,0 @@ -#Mon Nov 13 17:08:58 CST 2017 diff --git a/.gradle/2.14.1/taskArtifacts/cache.properties.lock b/.gradle/2.14.1/taskArtifacts/cache.properties.lock deleted file mode 100644 index 3849bb2..0000000 Binary files a/.gradle/2.14.1/taskArtifacts/cache.properties.lock and /dev/null differ diff --git a/.gradle/2.14.1/taskArtifacts/fileHashes.bin b/.gradle/2.14.1/taskArtifacts/fileHashes.bin deleted file mode 100644 index ed13957..0000000 Binary files a/.gradle/2.14.1/taskArtifacts/fileHashes.bin and /dev/null differ diff --git a/.gradle/2.14.1/taskArtifacts/fileSnapshots.bin b/.gradle/2.14.1/taskArtifacts/fileSnapshots.bin deleted file mode 100644 index 3f64a10..0000000 Binary files a/.gradle/2.14.1/taskArtifacts/fileSnapshots.bin and /dev/null differ diff --git a/.gradle/2.14.1/taskArtifacts/fileSnapshotsToTreeSnapshotsIndex.bin b/.gradle/2.14.1/taskArtifacts/fileSnapshotsToTreeSnapshotsIndex.bin deleted file mode 100644 index 42890fc..0000000 Binary files a/.gradle/2.14.1/taskArtifacts/fileSnapshotsToTreeSnapshotsIndex.bin and /dev/null differ diff --git a/.gradle/2.14.1/taskArtifacts/taskArtifacts.bin b/.gradle/2.14.1/taskArtifacts/taskArtifacts.bin deleted file mode 100644 index 2b7ed17..0000000 Binary files a/.gradle/2.14.1/taskArtifacts/taskArtifacts.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin b/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin deleted file mode 100644 index 77648bb..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock b/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock deleted file mode 100644 index d8c7d24..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin b/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin deleted file mode 100644 index c0ca8b7..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock b/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock deleted file mode 100644 index 170b9d2..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin b/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin deleted file mode 100644 index 14dce5e..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock b/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock deleted file mode 100644 index 2253236..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin b/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin deleted file mode 100644 index c0ca8b7..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock b/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock deleted file mode 100644 index c37fb37..0000000 Binary files a/.gradle/2.14.1/tasks/_app_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin b/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin deleted file mode 100644 index a2c1e39..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock b/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock deleted file mode 100644 index d00cdd0..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin b/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin deleted file mode 100644 index 7af99e9..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock b/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock deleted file mode 100644 index 6a6df9c..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileDebugJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin b/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin deleted file mode 100644 index a2c1e39..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock b/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock deleted file mode 100644 index f4d4084..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localClassSetAnalysis/localClassSetAnalysis.lock and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin b/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin deleted file mode 100644 index 7af99e9..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.bin and /dev/null differ diff --git a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock b/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock deleted file mode 100644 index 3781746..0000000 Binary files a/.gradle/2.14.1/tasks/_codeview_compileReleaseJavaWithJavac/localJarClasspathSnapshot/localJarClasspathSnapshot.lock and /dev/null differ diff --git a/.gradle/4.1/fileChanges/last-build.bin b/.gradle/4.1/fileChanges/last-build.bin deleted file mode 100644 index f76dd23..0000000 Binary files a/.gradle/4.1/fileChanges/last-build.bin and /dev/null differ diff --git a/.gradle/4.1/fileContent/fileContent.lock b/.gradle/4.1/fileContent/fileContent.lock deleted file mode 100644 index 331712f..0000000 Binary files a/.gradle/4.1/fileContent/fileContent.lock and /dev/null differ diff --git a/.gradle/4.1/fileHashes/fileHashes.bin b/.gradle/4.1/fileHashes/fileHashes.bin deleted file mode 100644 index 98148ec..0000000 Binary files a/.gradle/4.1/fileHashes/fileHashes.bin and /dev/null differ diff --git a/.gradle/4.1/fileHashes/fileHashes.lock b/.gradle/4.1/fileHashes/fileHashes.lock deleted file mode 100644 index b08d2ca..0000000 Binary files a/.gradle/4.1/fileHashes/fileHashes.lock and /dev/null differ diff --git a/.gradle/4.1/fileHashes/resourceHashesCache.bin b/.gradle/4.1/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 419003c..0000000 Binary files a/.gradle/4.1/fileHashes/resourceHashesCache.bin and /dev/null differ diff --git a/.gradle/4.1/javaCompile/classAnalysis.bin b/.gradle/4.1/javaCompile/classAnalysis.bin deleted file mode 100644 index 7354fdb..0000000 Binary files a/.gradle/4.1/javaCompile/classAnalysis.bin and /dev/null differ diff --git a/.gradle/4.1/javaCompile/jarAnalysis.bin b/.gradle/4.1/javaCompile/jarAnalysis.bin deleted file mode 100644 index ecab132..0000000 Binary files a/.gradle/4.1/javaCompile/jarAnalysis.bin and /dev/null differ diff --git a/.gradle/4.1/javaCompile/javaCompile.lock b/.gradle/4.1/javaCompile/javaCompile.lock deleted file mode 100644 index 193bde8..0000000 Binary files a/.gradle/4.1/javaCompile/javaCompile.lock and /dev/null differ diff --git a/.gradle/4.1/javaCompile/taskHistory.bin b/.gradle/4.1/javaCompile/taskHistory.bin deleted file mode 100644 index cc228bb..0000000 Binary files a/.gradle/4.1/javaCompile/taskHistory.bin and /dev/null differ diff --git a/.gradle/4.1/javaCompile/taskJars.bin b/.gradle/4.1/javaCompile/taskJars.bin deleted file mode 100644 index 147f808..0000000 Binary files a/.gradle/4.1/javaCompile/taskJars.bin and /dev/null differ diff --git a/.gradle/4.1/taskHistory/fileSnapshots.bin b/.gradle/4.1/taskHistory/fileSnapshots.bin deleted file mode 100644 index a9eac4e..0000000 Binary files a/.gradle/4.1/taskHistory/fileSnapshots.bin and /dev/null differ diff --git a/.gradle/4.1/taskHistory/taskHistory.bin b/.gradle/4.1/taskHistory/taskHistory.bin deleted file mode 100644 index ce0feab..0000000 Binary files a/.gradle/4.1/taskHistory/taskHistory.bin and /dev/null differ diff --git a/.gradle/4.1/taskHistory/taskHistory.lock b/.gradle/4.1/taskHistory/taskHistory.lock deleted file mode 100644 index 5befa74..0000000 Binary files a/.gradle/4.1/taskHistory/taskHistory.lock and /dev/null differ diff --git a/.gradle/buildOutputCleanup/built.bin b/.gradle/buildOutputCleanup/built.bin deleted file mode 100644 index e69de29..0000000 diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 908d02b..0000000 --- a/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Mon Jan 08 16:52:49 CST 2018 -gradle.version=4.1 diff --git a/.gradle/buildOutputCleanup/cache.properties.lock b/.gradle/buildOutputCleanup/cache.properties.lock deleted file mode 100644 index 40fdece..0000000 --- a/.gradle/buildOutputCleanup/cache.properties.lock +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/.idea/DataStructure.iml b/.idea/DataStructure.iml deleted file mode 100644 index dc1d2f2..0000000 --- a/.idea/DataStructure.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 1783ac1..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_animated_vector_drawable_23_4_0.xml b/.idea/libraries/com_android_support_animated_vector_drawable_23_4_0.xml deleted file mode 100644 index 0558c75..0000000 --- a/.idea/libraries/com_android_support_animated_vector_drawable_23_4_0.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_animated_vector_drawable_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_animated_vector_drawable_26_0_0_alpha1.xml deleted file mode 100644 index 44fd6d3..0000000 --- a/.idea/libraries/com_android_support_animated_vector_drawable_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_appcompat_v7_23_4_0.xml b/.idea/libraries/com_android_support_appcompat_v7_23_4_0.xml deleted file mode 100644 index a9871ad..0000000 --- a/.idea/libraries/com_android_support_appcompat_v7_23_4_0.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_appcompat_v7_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_appcompat_v7_26_0_0_alpha1.xml deleted file mode 100644 index f3e5c26..0000000 --- a/.idea/libraries/com_android_support_appcompat_v7_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_constraint_constraint_layout_1_0_2.xml b/.idea/libraries/com_android_support_constraint_constraint_layout_1_0_2.xml deleted file mode 100644 index be722fc..0000000 --- a/.idea/libraries/com_android_support_constraint_constraint_layout_1_0_2.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_constraint_constraint_layout_solver_1_0_2_jar.xml b/.idea/libraries/com_android_support_constraint_constraint_layout_solver_1_0_2_jar.xml deleted file mode 100644 index 2e7dc57..0000000 --- a/.idea/libraries/com_android_support_constraint_constraint_layout_solver_1_0_2_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_annotations_23_4_0_jar.xml b/.idea/libraries/com_android_support_support_annotations_23_4_0_jar.xml deleted file mode 100644 index b902411..0000000 --- a/.idea/libraries/com_android_support_support_annotations_23_4_0_jar.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_annotations_26_0_0_alpha1_jar.xml b/.idea/libraries/com_android_support_support_annotations_26_0_0_alpha1_jar.xml deleted file mode 100644 index d6e552c..0000000 --- a/.idea/libraries/com_android_support_support_annotations_26_0_0_alpha1_jar.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_compat_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_compat_26_0_0_alpha1.xml deleted file mode 100644 index 2a60df8..0000000 --- a/.idea/libraries/com_android_support_support_compat_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_core_ui_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_core_ui_26_0_0_alpha1.xml deleted file mode 100644 index b5af75a..0000000 --- a/.idea/libraries/com_android_support_support_core_ui_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_core_utils_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_core_utils_26_0_0_alpha1.xml deleted file mode 100644 index 35821c5..0000000 --- a/.idea/libraries/com_android_support_support_core_utils_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_fragment_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_fragment_26_0_0_alpha1.xml deleted file mode 100644 index 9365398..0000000 --- a/.idea/libraries/com_android_support_support_fragment_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_media_compat_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_media_compat_26_0_0_alpha1.xml deleted file mode 100644 index 6706aab..0000000 --- a/.idea/libraries/com_android_support_support_media_compat_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_v4_23_4_0.xml b/.idea/libraries/com_android_support_support_v4_23_4_0.xml deleted file mode 100644 index 2a44419..0000000 --- a/.idea/libraries/com_android_support_support_v4_23_4_0.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_v4_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_v4_26_0_0_alpha1.xml deleted file mode 100644 index 083f6c1..0000000 --- a/.idea/libraries/com_android_support_support_v4_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_vector_drawable_23_4_0.xml b/.idea/libraries/com_android_support_support_vector_drawable_23_4_0.xml deleted file mode 100644 index 9a33f8a..0000000 --- a/.idea/libraries/com_android_support_support_vector_drawable_23_4_0.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_support_vector_drawable_26_0_0_alpha1.xml b/.idea/libraries/com_android_support_support_vector_drawable_26_0_0_alpha1.xml deleted file mode 100644 index 113156c..0000000 --- a/.idea/libraries/com_android_support_support_vector_drawable_26_0_0_alpha1.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_test_espresso_espresso_core_2_2_2.xml b/.idea/libraries/com_android_support_test_espresso_espresso_core_2_2_2.xml deleted file mode 100644 index fdaf01b..0000000 --- a/.idea/libraries/com_android_support_test_espresso_espresso_core_2_2_2.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_test_espresso_espresso_idling_resource_2_2_2.xml b/.idea/libraries/com_android_support_test_espresso_espresso_idling_resource_2_2_2.xml deleted file mode 100644 index 5f9a13f..0000000 --- a/.idea/libraries/com_android_support_test_espresso_espresso_idling_resource_2_2_2.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_test_exposed_instrumentation_api_publish_0_5.xml b/.idea/libraries/com_android_support_test_exposed_instrumentation_api_publish_0_5.xml deleted file mode 100644 index 7c4d7ca..0000000 --- a/.idea/libraries/com_android_support_test_exposed_instrumentation_api_publish_0_5.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_test_rules_0_5.xml b/.idea/libraries/com_android_support_test_rules_0_5.xml deleted file mode 100644 index 5c0e4e7..0000000 --- a/.idea/libraries/com_android_support_test_rules_0_5.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_android_support_test_runner_0_5.xml b/.idea/libraries/com_android_support_test_runner_0_5.xml deleted file mode 100644 index ad6ad9f..0000000 --- a/.idea/libraries/com_android_support_test_runner_0_5.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_google_code_findbugs_jsr305_2_0_1_jar.xml b/.idea/libraries/com_google_code_findbugs_jsr305_2_0_1_jar.xml deleted file mode 100644 index 6343119..0000000 --- a/.idea/libraries/com_google_code_findbugs_jsr305_2_0_1_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/com_squareup_javawriter_2_1_1_jar.xml b/.idea/libraries/com_squareup_javawriter_2_1_1_jar.xml deleted file mode 100644 index 3703651..0000000 --- a/.idea/libraries/com_squareup_javawriter_2_1_1_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/javax_annotation_javax_annotation_api_1_2_jar.xml b/.idea/libraries/javax_annotation_javax_annotation_api_1_2_jar.xml deleted file mode 100644 index e717813..0000000 --- a/.idea/libraries/javax_annotation_javax_annotation_api_1_2_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/javax_inject_javax_inject_1_jar.xml b/.idea/libraries/javax_inject_javax_inject_1_jar.xml deleted file mode 100644 index d4b7ed2..0000000 --- a/.idea/libraries/javax_inject_javax_inject_1_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/junit_junit_4_12_jar.xml b/.idea/libraries/junit_junit_4_12_jar.xml deleted file mode 100644 index a026090..0000000 --- a/.idea/libraries/junit_junit_4_12_jar.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/org_hamcrest_hamcrest_core_1_3_jar.xml b/.idea/libraries/org_hamcrest_hamcrest_core_1_3_jar.xml deleted file mode 100644 index 8bc8974..0000000 --- a/.idea/libraries/org_hamcrest_hamcrest_core_1_3_jar.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/org_hamcrest_hamcrest_integration_1_3_jar.xml b/.idea/libraries/org_hamcrest_hamcrest_integration_1_3_jar.xml deleted file mode 100644 index 9af9fd3..0000000 --- a/.idea/libraries/org_hamcrest_hamcrest_integration_1_3_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/org_hamcrest_hamcrest_library_1_3_jar.xml b/.idea/libraries/org_hamcrest_hamcrest_library_1_3_jar.xml deleted file mode 100644 index da36770..0000000 --- a/.idea/libraries/org_hamcrest_hamcrest_library_1_3_jar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/org_jsoup_jsoup_1_9_2_jar.xml b/.idea/libraries/org_jsoup_jsoup_1_9_2_jar.xml deleted file mode 100644 index 1a2fe37..0000000 --- a/.idea/libraries/org_jsoup_jsoup_1_9_2_jar.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index e10baaa..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - Android - - - Android > Lint > Correctness - - - Android > Lint > Performance - - - Android > Lint > Usability > Icons - - - Code style issuesJava - - - Java - - - - - - - - - - - - - - - - - - 1.8 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 1bbef2c..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460..0000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 2ce6ac6..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,4030 +0,0 @@ - - - - - - android-26 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if \(TextUtils\.isEmpty\(result\)\)\{\n tvResult\.setVisibility\(View\.GONE\)\;\n tvResultMenu\.setVisibility\(View\.GONE\)\;\n \} - private - s - setTit - setTitl - setTitle - etTitle - directInsertSort - System.out.println( - Console.WriteLine( - Start(); - SelectIndexDataActivityTest - tvData - btnRun - runBtn - testInteruptThread - addIm - ' - getPosition - QuickSort - activity - left - addItem - Recursion - - - protected - Log.i("info", - start(); - " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $USER_HOME$/.subversion - - - - - 1510560694935 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.push.sh.swp b/.push.sh.swp deleted file mode 100644 index 5bb69ba..0000000 Binary files a/.push.sh.swp and /dev/null differ diff --git a/DataStructure.iml b/DataStructure.iml deleted file mode 100644 index dc1d2f2..0000000 --- a/DataStructure.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index cbce4b1..fd556b0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## 数据结构与算法学习之路---Java +## 数据结构与算法学习之路 ##### 下面的算法都打包在一个应用当中,你只需要下载安装即可,里面有算法的介绍,时间复杂度,空间复杂度,代码示例 @@ -13,36 +13,179 @@ - [选择排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/OptionSortActivity.java) - [冒泡排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/BubbleSortActivity.java) - [线程与锁详解](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/java/JavaThreadActivity.java) +- [二叉树的性质](https://github.com/UCodeUStory/DataStructure/blob/master/sources/binearyTree.md) - 二叉树的遍历 -- [二叉排序树]() +- 二叉排序树 +- 红黑树 +- AVL树 - [图的详解](https://github.com/UCodeUStory/DataStructure/blob/master/sources/tu.md) - 图的邻接表存储构成图 -- 无向图的邻接表存储-深度优先搜索 -- 无向图的邻接表存储-广度优先搜索 -- 无向图的邻接矩阵存储-深度优先搜索 -- 无向图的邻接矩阵存储-广度优先搜索 +- [无向图的邻接表存储-深度优先搜索](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraph.java) +- [无向图的邻接表存储-广度优先搜索](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraph.java) +- [无向图的邻接矩阵存储-深度优先搜索](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraphMatrix.java) +- [无向图的邻接矩阵存储-广度优先搜索](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraphMatrix.java) - 有向图的创建 -- 拓扑排序-邻接矩阵存储-Kahn算法 +- [拓扑排序-邻接矩阵存储-Kahn算法](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/graph/TopologicalOrderActivity.java) - 拓扑排序-邻接矩阵存储-深度优先搜索算法 +- [邻接矩阵和邻接表比较](https://github.com/UCodeUStory/DataStructure/blob/master/sources/matrix_table.md) - [最短路径算法之Dijkstra算法(狄克斯特拉算法](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/graph/DjstaActivity.java) -- 最短路径算法 -- ArrayList实现 -- LinkList双向实现 -- 堆排序 -- 归并排序 -- 希尔排序 -- 八大排序总结 -- 计数排序 -- 基数排序(以后再加,不常用) -- 桶排序 -- 同时找出最大值和最小值最优算法 -- 快速查找法,查找第k个最大的数 -- 10亿数据查找前100个 +- [ArrayList实现原理](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/java/mylist/CJArrayList.java) +- [LinkList双向实现](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/java/mylist/CJArrayList.java) +- [堆排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/HeapSortActivity.java) +- [归并排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/MergeSortActivity.java) +- [希尔排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/ShellSortActivity.java) +- [八大排序总结](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/EightSortDescriptionActivity.java) +- [计数排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/CountSortActivity.java) +- [同时找出最大值和最小值最优算法](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/MaxMinSelectActivity.java) +- [快速查找法,查找第k个最大的数](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/SelectIndexDataActivity.java) +- [10亿数据查找前100个](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/sort/MaxDataSelectDataActivity.java) - [散列表(哈希表)](https://github.com/UCodeUStory/DataStructure/blob/master/hashtable.md) - [求最大不重复子串](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/other/MaxSubStringActivity.java) -- 两个排序数组的中值 +- [死锁](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/java/DeadLockDemo.java) +- [两个线程交替输出1010](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/java/ThreadOneZero.java) +#### 算法设计常用思想 +- [贪婪算法之找零钱问题](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy.java) +- [贪婪算法之01背包问题](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy01Bag.java) +- [分治算法之二分法查找](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/BinearySearch.kt) +- [分治算法之字符串全排列](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/StringFullArrangement.kt) +- [分治算法之快速排序](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/QuickSort.kt) +- [分治算法之合并排序] +- [迭代算法之欧几里得算法](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/IterationGCD.kt) +- [迭代算法和递归算法区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/iterationAndroidrecursion.md) +- [迭代算法之二分逼近法](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/TwoSeparate.kt) +- [动态规划法之01背包问题](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgram.kt) +- [动态规划法之装配站问题] +- [穷举算法之百钱买鸡](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion100buychicken.kt) +- [穷举算法之鸡兔同笼](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionchickenrabbit.kt) +- [穷举算法之853桶分4升水的分法有多少种](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionbucket.kt) +- [穷举算法之爱因斯坦的思考](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionAlbertEinstein.kt) +- [穷举加分治算法之24点计算](https://github.com/UCodeUStory/DataStructure/blob/master/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion24.kt) +#### Java 常见问题 +- [Java 基础面试题](https://github.com/UCodeUStory/DataStructure/blob/master/sources/javabasic.md) +- [Java垃圾回收机制](https://github.com/UCodeUStory/DataStructure/blob/master/sources/JavaGarbageCollection.md) +- [Java 强、软、弱、虚引用](https://github.com/UCodeUStory/DataStructure/blob/master/sources/reference.md) +- [Java 线程池实现原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/thread_principle.md) +- [weakHashMap](https://github.com/UCodeUStory/DataStructure/blob/master/sources/weakHashMap.md) +- [tomcat 旧版本 使用currentHashMap和weakHashMap做分代缓存](https://github.com/UCodeUStory/DataStructure/blob/master/sources/tomcat_cache.java) +- [tomcat 新版本 使用LRU缓存,最后也被废弃了](https://github.com/UCodeUStory/DataStructure/blob/master/sources/tomcat_lru_cache.java) +- [Java 实现LRU缓存3种方式 ](https://github.com/UCodeUStory/DataStructure/blob/master/sources/lru.md) +- [LinkHashMap 实现FIFO](https://github.com/UCodeUStory/DataStructure/blob/master/sources/fifo.md) +- [Java 集合原理](http://wiki.jikexueyuan.com/project/java-collection/hashset.html) +- [wait 和 sleep区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/wait_sleep.md) +- [yield()和join()](https://github.com/UCodeUStory/DataStructure/blob/master/sources/yield_join.md) +- [HashMap冲突](https://github.com/UCodeUStory/DataStructure/blob/master/sources/hash_confict.md) +- [Java 8十大特性](https://github.com/UCodeUStory/DataStructure/blob/master/sources/java8.md) +- [Java 克隆](https://github.com/UCodeUStory/DataStructure/blob/master/sources/javaCopy.md) +- [Java foreach原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/foreach.md) +- [synchronized 和 Lock区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/synchronized_lock.md) +- [BlockingQueue和BlockingDequeu详解](https://github.com/UCodeUStory/DataStructure/blob/master/sources/blockqueue.md) +- [锁类型详解](https://github.com/UCodeUStory/DataStructure/blob/master/sources/locktype.md) +- [Condition实现对锁进行更精确的控制](https://github.com/UCodeUStory/DataStructure/blob/master/sources/condition.md) +- [LinkBlockingQueue原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/linkblockingqueue.md) +- [多个线程安全的原子化操作组合将不再是安全的](https://github.com/UCodeUStory/DataStructure/blob/master/sources/automizationoperation.md) +- [CopyOnWriteArrayList原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/copyOnWriteArrayList.md) +- [ConcurrentHashMap原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/concurrentHashMap.md) +- [分布式锁-分段锁思想] +- [Atomic包的原理及分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/javaconcurrent/atomic.md) +- [CAS实现自旋线程安全](https://github.com/UCodeUStory/DataStructure/blob/master/sources/javaconcurrent/selfRotate.md) +- [Java泛型使用](https://github.com/UCodeUStory/DataStructure/blob/master/sources/javageneric.md) +- Java NIO - +#### Android 常见问题 +1. [Volley源码分析经典算法](https://github.com/UCodeUStory/DataStructure/blob/master/sources/volley_algorithm.md) +2. [Android Design Support Library包含内容](https://github.com/UCodeUStory/DataStructure/blob/master/sources/adsl.md) +3. [Android v4 v7 v8 v13区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/v4_v7_v8_v13.md) +4. [Android Design Support Library V28 新增加内容](https://github.com/UCodeUStory/DataStructure/blob/master/sources/design_v28.md) +5. [Android网络数据安全](https://github.com/UCodeUStory/DataStructure/blob/master/sources/netsafe.md) +6. [Android Binder 原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/binder.md) +7. [Android应用架构设计](https://github.com/UCodeUStory/DataStructure/blob/master/sources/frame.md) +8. [热修复技术和原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/hotfix.md) +9. [Android 8.0 WorkManager后台任务可以保活](https://github.com/UCodeUStory/DataStructure/blob/master/sources/workManager.md) +10. [JNI双进程保活] +11. [Android 应用启动流程](https://github.com/UCodeUStory/DataStructure/blob/master/sources/app_start_step.md) +12. [Activity和Fragment和Service生命周期] +13. [Handler机制] +14. [AsyncTask源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/asynctask.md) +15. [Android 图片下载](https://github.com/UCodeUStory/DataStructure/blob/master/sources/imagedownload.md) +16. [OnNewIntent 什么时候被调用](https://github.com/UCodeUStory/DataStructure/blob/master/sources/activity_onnewIntent.md) +17. [Android两种虚拟机区别和联系](https://github.com/UCodeUStory/DataStructure/blob/master/sources/davik_art.md) +18. [View的源码分析(绘制流程以及刷新机制)](https://github.com/UCodeUStory/DataStructure/blob/master/sources/view_root.md) +19. [RecyclerView 和ListView区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/recyclerView_listview.md) +20. [volatile原理](https://github.com/UCodeUStory/DataStructure/blob/master/sources/volatile.md) +21. [Fragment的懒加载实现](https://github.com/UCodeUStory/DataStructure/blob/master/sources/fragment_lazy_load.md) +22. [requestlayout,invalidate,postInvalidate 区别于联系](https://github.com/UCodeUStory/DataStructure/blob/master/sources/requestlayout_invalidate_postInvalidate.md) +23. [OnMeasure多次调用问题](https://github.com/UCodeUStory/DataStrucgitture/blob/master/sources/onMeasure.md) +24. ViewPager缓存原理 +25. [多进程Application初始化问题](https://github.com/UCodeUStory/DataStructure/blob/master/sources/application.md) +26. [Application可以开线程替换Service处理后台任务吗](https://github.com/UCodeUStory/DataStructure/blob/master/sources/application_service.md) +27. [android.os.killProcess和System.exit(0)区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/killprocess_system_exit.md) +28. [线程通信有哪些方式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/thread_communication_way.md) +29. [ConstraintLayout 完全解析 快来优化你的布局](https://blog.csdn.net/lmj623565791/article/details/78011599?utm_source=tuicool&utm_medium=referral) +30. [Android 匿名共享内存原理](https://www.jianshu.com/p/d9bc9c668ba6) +31. [Binder 原理深度剖析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/binder.md) +32. [MediaPlayer生命周期](https://github.com/UCodeUStory/DataStructure/blob/master/sources/media_player.md) +33. [TransactionTooLargeException解决方法](https://github.com/UCodeUStory/DataStructure/blob/master/sources/transactiontoolargeexception.md) +34. [谈一下Http请求过程](https://github.com/UCodeUStory/DataStructure/blob/master/sources/http.md) +35. [Android 进程通信种类](https://github.com/UCodeUStory/DataStructure/blob/master/sources/androidipc.md) +36. [面试题如何计算View的深度,写一段成程序](https://github.com/UCodeUStory/DataStructure/blob/master/sources/androidinterview/viewdeep.md) +37. [统计一个ViewGroup中包含的子View的个数(递归和非递归实现)](https://github.com/UCodeUStory/DataStructure/blob/master/sources/androidinterview/viewcount.md) +38. [返回一个在ViewGroup下面的一个View,id为方法的第二个参数](https://github.com/UCodeUStory/DataStructure/blob/master/sources/androidinterview/viewgroupfindview.md) +#### 设计模式 +1. [面向对象的七种设计原则](https://github.com/UCodeUStory/DataStructure/blob/master/sources/seven_design_principles.md) +2. [建造者模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/kotlin/builderModel.kt) +3. [命令模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/commandPattern.md) +4. [享元模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/flyweightpattern.md) +5. [模板方法模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/templateMethod.md) +6. [责任链模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/chainofResponsibility.md) +7. [建造者模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/builder.md) +8. [原型模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/prototype.md) +9. [观察者模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/observer.md) +10. [策略模式与命令模式区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/stragetyAndcommanddiff.md) +11. [桥接模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/bridge.md) +12. [组合模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/composite.md) +13. [适配器模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/adapter.md) +14. [装饰者模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/decorator.md) +15. [外观模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/facade.md) +16. [状态模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/state.md) +17. [状态模式与策略模式区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/state.md) +18. [迭代器模式]基本用不到 +19. [备忘模式]很简单、就是有个管理员保存一些对象 +20. [访问者模式] 不常用,不好用,不信你擦擦 +21. [中介模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/mediator.md) +22. [解释器模式] +23. [代理模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/proxy.md) +24. [简单工厂模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/simpleFactory.md) +25. [工厂模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/factory.md) +26. [抽象工厂](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/abstractfactory.md) +27. [委托设计模式](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/delegation.md) +28. [适配器模式 与(装饰者、代理模式)区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/adapterdiffdecorator.md) +29. [装饰器模式和代理模式区别](https://github.com/UCodeUStory/DataStructure/blob/master/sources/designpattern/proxyidiffdecorator.md) + + +#### Android 框架使用说明 +1. [Rxjava使用](https://github.com/UCodeUStory/DataStructure/blob/master/sources/rxjavademo.md) +2. [LiveData](https://github.com/UCodeUStory/DataStructure/blob/master/sources/livedata.md) +3. [RxCache] + +##### Android框架源码分析 +1. [EventBus源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/eventbus.md) +2. [Bufferknife源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/butterknife.md) +3. [Glide 源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/glide.md) +4. [Picasso 源码分析] +5. [OKHttp 源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/okhttp.md) +6. [Retrofit 源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/retrofit.md) +7. [ARouter 源码分析] +8. [LeakCanary 源码分析] +9. [Blockcanary 源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/blockcanary.md) +10. [Lifecycler源码分析] +11. [RxJava 源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/androidopensources/rxjavasource.md) +12. [ViewModel 源码分析](https://github.com/UCodeUStory/DataStructure/blob/master/sources/viewmodel.md) + + + +
+ +
diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index 668cc1d..0000000 --- a/app/app.iml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 62375b6..4b2dc58 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,21 +1,52 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + compileSdkVersion 28 + defaultConfig { applicationId "com.wangpos.datastructure" minSdkVersion 15 - targetSdkVersion 26 + targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } +// buildTypes { +// release { +// minifyEnabled false +// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' +// } +// } + + + signingConfigs { + releaseConfig { + keyAlias 'androiddebugkey' + keyPassword 'android' + storeFile file('/Users/qiyue/wangpos/code3/keystore/debug.keystore') + storePassword 'android' + } + + + } + buildTypes { release { + buildConfigField "boolean", "LOG_DEBUG", "false" + buildConfigField "boolean", "DEBUG_MODEL", "false" minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + + debug { + buildConfigField "boolean", "LOG_DEBUG", "false" + buildConfigField "boolean", "DEBUG_MODEL", "true" + minifyEnabled false + + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } } } @@ -25,7 +56,14 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:26.0.0-alpha1' testCompile 'junit:junit:4.12' compile project(':codeview') + implementation 'com.android.support:appcompat-v7:28.0.0-alpha1' + implementation 'com.android.support:design:28.0.0-alpha1' + + implementation 'com.squareup.okhttp3:okhttp:3.11.0' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5184087..f3d45bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/normal.html b/app/src/main/assets/normal.html new file mode 100644 index 0000000..d781bdf --- /dev/null +++ b/app/src/main/assets/normal.html @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/main/assets/pugongying.mp3 b/app/src/main/assets/pugongying.mp3 new file mode 100644 index 0000000..caa5fe4 Binary files /dev/null and b/app/src/main/assets/pugongying.mp3 differ diff --git a/app/src/main/assets/sound_test.mp3 b/app/src/main/assets/sound_test.mp3 new file mode 100644 index 0000000..1d2a43e Binary files /dev/null and b/app/src/main/assets/sound_test.mp3 differ diff --git a/app/src/main/java/com/wangpos/datastructure/MainActivity.java b/app/src/main/java/com/wangpos/datastructure/MainActivity.java index 0ea8b7a..421d6a2 100644 --- a/app/src/main/java/com/wangpos/datastructure/MainActivity.java +++ b/app/src/main/java/com/wangpos/datastructure/MainActivity.java @@ -1,21 +1,31 @@ package com.wangpos.datastructure; +import android.content.ComponentName; import android.content.Intent; +import android.os.AsyncTask; +import android.os.Build; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import com.wangpos.datastructure.core.DataBean; +import com.wangpos.datastructure.core.USList; import com.wangpos.datastructure.core.WebViewActivity; import com.wangpos.datastructure.graph.DjstaActivity; import com.wangpos.datastructure.graph.GraphActivity; import com.wangpos.datastructure.graph.TopologicalOrderActivity; +import com.wangpos.datastructure.interview.DesignV28Activity; import com.wangpos.datastructure.java.JavaThreadActivity; +import com.wangpos.datastructure.java.JavaThreadPrincipleActivity; import com.wangpos.datastructure.java.JavaThreadSummary; import com.wangpos.datastructure.java.JavaWaitNotifyActivity; import com.wangpos.datastructure.java.KeepMoreRequest; +import com.wangpos.datastructure.java.LockTestActivity; +import com.wangpos.datastructure.java.ReferenceQueueActivity; +import com.wangpos.datastructure.other.GetMinActivity; import com.wangpos.datastructure.other.MaxSubStringActivity; import com.wangpos.datastructure.sort.BinaryTreeActivity; import com.wangpos.datastructure.sort.BisearchActivity; @@ -37,8 +47,19 @@ import com.wangpos.datastructure.sort.SpaceComplexityActivity; import com.wangpos.datastructure.sort.TimeComplexityActivity; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import okhttp3.Cache; +import okhttp3.ConnectionPool; +import okhttp3.Dispatcher; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Response; import static com.wangpos.datastructure.core.BaseActivity.setWindowStatusBarColor; @@ -110,6 +131,16 @@ public class MainActivity extends AppCompatActivity { public static final int Djsta = 33; + public static final int JavaReferenceQueue = 34; + + public static final int JavaThreadPrinciple = 35; + + public static final int DesignV28 = 36; + + public static final int GetMin = 37; + + public static final int LOCKTEST = 38; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -119,7 +150,7 @@ protected void onCreate(Bundle savedInstanceState) { setWindowStatusBarColor(this,R.color.colorPrimary); ListView mListView = (ListView)findViewById(R.id.listView); final ListAdapter adapter = new ListAdapter(this); - Listlist = new ArrayList<>(); + USListlist = new USList<>(); initData(list); @@ -133,14 +164,15 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { } }); + + + } private void onClickItem(DataBean data) { switch (data.type){ case ToGitHub: - Intent toGitHubIntent = new Intent(this, WebViewActivity.class); - toGitHubIntent.putExtra(WebViewActivity.EXTRA_URL,"https://github.com/UCodeUStory"); - startActivity(toGitHubIntent); + launchWebView("https://github.com/UCodeUStory"); break; case TimeComplexity: startActivity(new Intent(this,TimeComplexityActivity.class)); @@ -208,30 +240,20 @@ private void onClickItem(DataBean data) { case MaxDataSelectData: startActivity(new Intent(this,MaxDataSelectDataActivity.class)); case HashTable: - Intent hashIntent = new Intent(this, WebViewActivity.class); - hashIntent.putExtra(WebViewActivity.EXTRA_URL,"https://github.com/UCodeUStory/DataStructure/blob/master/hashtable.md"); - startActivity(hashIntent); + launchWebView("https://github.com/UCodeUStory/DataStructure/blob/master/hashtable.md"); break; case binarySearchTree: - Intent binarySearchIntent = new Intent(this, WebViewActivity.class); - binarySearchIntent.putExtra(WebViewActivity.EXTRA_URL,"https://github.com/UCodeUStory/DataStructure/blob/master/sources/tree.md"); - startActivity(binarySearchIntent); + launchWebView("https://github.com/UCodeUStory/DataStructure/blob/master/sources/tree.md"); break; case Singleton: - Intent singletonIntent = new Intent(this, WebViewActivity.class); - singletonIntent.putExtra(WebViewActivity.EXTRA_URL,"https://github.com/UCodeUStory/DataStructure/blob/master/sources/singleInstance.md"); - startActivity(singletonIntent); + launchWebView("https://github.com/UCodeUStory/DataStructure/blob/master/sources/singleInstance.md"); break; case TU: - Intent tuIntent = new Intent(this, WebViewActivity.class); - tuIntent.putExtra(WebViewActivity.EXTRA_URL,"https://github.com/UCodeUStory/DataStructure/blob/master/sources/tu.md"); - startActivity(tuIntent); + launchWebView("https://github.com/UCodeUStory/DataStructure/blob/master/sources/tu.md"); break; case JavaGC: - Intent javaGcIntent = new Intent(this, WebViewActivity.class); - javaGcIntent.putExtra(WebViewActivity.EXTRA_URL,"https://github.com/UCodeUStory/DataStructure/blob/master/sources/JavaGarbageCollection.md"); - startActivity(javaGcIntent); + launchWebView("https://github.com/UCodeUStory/DataStructure/blob/master/sources/JavaGarbageCollection.md"); break; case MAXSubString: startActivity(new Intent(this, MaxSubStringActivity.class)); @@ -248,13 +270,33 @@ private void onClickItem(DataBean data) { break; case Djsta: startActivity(new Intent(this,DjstaActivity.class)); + case JavaReferenceQueue: + startActivity(new Intent(this,ReferenceQueueActivity.class)); + break; + case JavaThreadPrinciple: + startActivity(new Intent(this,JavaThreadPrincipleActivity.class)); + break; + case DesignV28: + startActivity(new Intent(this,DesignV28Activity.class)); + break; + case GetMin: + startActivity(new Intent(this, GetMinActivity.class)); + break; + case LOCKTEST: + startActivity(new Intent(this, LockTestActivity.class)); default: break; } } - private void initData(List list) { + private void launchWebView(String s) { + Intent tuIntent = new Intent(this, WebViewActivity.class); + tuIntent.putExtra(WebViewActivity.EXTRA_URL, s); + startActivity(tuIntent); + } + + private void initData(USList list) { list.add(new DataBean(ToGitHub,"进入作者GitHub")); list.add(new DataBean(TimeComplexity,"时间复杂度介绍")); list.add(new DataBean(SpaceComplexity,"空间复杂度介绍")); @@ -287,7 +329,16 @@ private void initData(List list) { list.add(new DataBean(MAXSubString,"求最大不重复子串")); list.add(new DataBean(Graph,"图的算法")); list.add(new DataBean(TopologicalSort,"拓扑排序")); - list.add(new DataBean(Djsta,"最短路径算法1")); + list.add(new DataBean(Djsta,"最短路径算法Djsta")); + list.add(new DataBean(JavaReferenceQueue,"ReferenceQueue")); + list.add(new DataBean(JavaThreadPrinciple,"Java线程池原理")); + list.add(new DataBean(DesignV28,"AndroidDesignV28新增内容")); + list.add(new DataBean(GetMin,"通过栈实现获取最小值")); + list.add(new DataBean(LOCKTEST,"LOCK使用")); + + //Debug + +// list.add(0,list.get(list.size()-1)); } diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/BinearySearch.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/BinearySearch.kt new file mode 100644 index 0000000..08e6b5d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/BinearySearch.kt @@ -0,0 +1,52 @@ +package com.wangpos.datastructure.algorithm + +/** + * 分治法,顾名思义分而治之,将无法着手的大问题分解成一系列相同的小问题,然后逐一解决 + * + * 分治算法基本上可以归纳为三个步骤 + * + * 1. 分解:将问题分解为若干规模小的相同结构问题 + * + * 2. 解决:如果上一步问题可以解决就解决,否则,对每个子问题使用和上一步相同的方法再次分解,然后求解分解后的子问题,这个过程可能是一个递归的过程 + * + * 3. 合并:将上一步解决的各个子问题的解通过某种规则合并起来,得到原问题的解 + * + * 分治法难点,如果将子问题分解,并将子问题的解合并,针对不同的问题,有不同的分解与合并的方式 + * + * 递归作为一种算法的实现方式,与分治是一✅天然的好朋友。当然分治也可以用非递归方式实现出来,就是比较难 + * + * + */ + +fun main(args: Array) { + + var arrays = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + + var target = 7 + + var index = binearySearch(arrays, 0, arrays.size, target) + + println("被查询的对象:$target") + println("查询到的位置:$index") + println("查询到的数据:${arrays[index]}") +} + + +/** + * 二分法查找 + */ +fun binearySearch(arrays: Array, start: Int, end: Int, target: Int): Int { + + var middleIndex = (start + end) / 2 + var middleValue = arrays[middleIndex] + + if (target == middleValue) { + return middleIndex + } else if (target < middleValue) { + return binearySearch(arrays, start, middleIndex - 1, target) + } else if (target > middleValue) { + return binearySearch(arrays, middleIndex + 1, end, target) + } + return -1 +} + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DivideAndConquer.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/DivideAndConquer.kt new file mode 100644 index 0000000..b438da2 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DivideAndConquer.kt @@ -0,0 +1,34 @@ +package com.wangpos.datastructure.algorithm + + +/** + * 分治法,顾名思义分而治之,将无法着手的大问题分解成一系列相同的小问题,然后逐一解决 + * + * 分治算法基本上可以归纳为三个步骤 + * + * 1. 分解:将问题分解为若干规模小的相同结构问题 + * + * 2. 解决:如果上一步问题可以解决就解决,否则,对每个子问题使用和上一步相同的方法再次分解,然后求解分解后的子问题,这个过程可能是一个递归的过程 + * + * 3. 合并:将上一步解决的各个子问题的解通过某种规则合并起来,得到原问题的解 + * + * 分治法难点,如果将子问题分解,并将子问题的解合并,针对不同的问题,有不同的分解与合并的方式 + * + * 递归作为一种算法的实现方式,与分治是一✅天然的好朋友。当然分治也可以用非递归方式实现出来,就是比较难 + * + * + */ +fun main(args: Array) { + + var arrays = arrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + + var target = 7 + + var index = binearySearch(arrays, 0, arrays.size, target) + + println("被查询的对象:$target") + println("查询到的位置:$index") + println("查询到的数据:${arrays[index]}") +} + + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicCirsscrossString.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicCirsscrossString.java new file mode 100644 index 0000000..2171866 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicCirsscrossString.java @@ -0,0 +1,53 @@ +package com.wangpos.datastructure.algorithm; + +public class DynamicCirsscrossString { + public static final int ROW = 3; + public static final int COLUMN = 3; + + public static void main(String[] args) { + + + int map[][] = new int[][]{{-2, -3, 3}, {-5, -10, 1}, {0, 30, -5}}; + int result = getMinBlood(map); + System.out.println("最少血量" + result); + } + + + public static int getMinBlood(int[][] map) { + + int dp[][] = new int[ROW][COLUMN]; + + //初始化第一行 + dp[0][0] = 1 + getCurrentPositionNeedBlood(map[0][0]); + for (int i = 1; i < ROW; i++) { + dp[0][i] = dp[0][i-1] + getCurrentPositionNeedBlood(map[0][i]); + } + for(int j =1;j arr[j]) { + //这个公式你要品,你细品 + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + + System.out.println(Arrays.toString(dp)); + + int max = 0; + int maxIndex = 0; + //找到最大值 + for (int k = dp.length - 1; k >= 0; k--) { + if (dp[k] > max) { + max = dp[k]; + maxIndex = k; + } + } + + System.out.println("max=" + max + "maxIndex=" + maxIndex); + System.out.println(arr[maxIndex]); + for (int m = maxIndex; m >= 0; m--) { + if (arr[m] < arr[maxIndex] && dp[m] == dp[maxIndex] - 1) { + maxIndex = m; + System.out.println(arr[m]); + } + } + } + + // O N*N; 通过二分查找优化成Ologn + + /** + * 通过递增子串的规则,我们发现 + *

+ * 每次我们都找一个最小 + * + * @param arr + */ + private static void findTwoSplit(int[] arr) { + //存储第i个位置结尾,最长递增子串长度 + + //根据最后一个dp 值向前遍历,找打小于他的一个值,并且dp[i] = dp[7]-1 + + int dp[] = new int[arr.length]; + //有效值 + int ends[] = new int[arr.length]; + ends[0] = arr[0]; + dp[0] = 1; + // 0 right有效区 + int right = 0; + for (int i = 1; i < arr.length; i++) { + int l = 0; + int r = right; + while (l <= r) { + int mid = (l + r) / 2; + if (arr[i] > ends[mid]) { + l = mid + 1; + } else { + r = mid - 1; + } + } + if (l > right) {//有效区扩张 + right = l; + } + ends[l] = arr[i]; + dp[i] = l + 1; + +// for (int j = 0; j < i; j++) { +// //与前面每一个比较如果大于就比较一下对应dp值是否是最大的赋值给当前 +// if (arr[i] > arr[j]) { +// //这个公式你要品,你细品 +// dp[i] = Math.max(dp[i], dp[j] + 1); +// } +// } + } + + System.out.println(Arrays.toString(dp)); + + int max = 0; + int maxIndex = 0; + //找到最大值 + for (int k = dp.length - 1; k >= 0; k--) { + if (dp[k] > max) { + max = dp[k]; + maxIndex = k; + } + } + + System.out.println("max=" + max + "maxIndex=" + maxIndex); + System.out.println(arr[maxIndex]); + for (int m = maxIndex; m >= 0; m--) { + if (arr[m] < arr[maxIndex] && dp[m] == dp[maxIndex] - 1) { + maxIndex = m; + System.out.println(arr[m]); + } + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMaxLongSubSequence.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMaxLongSubSequence.java new file mode 100644 index 0000000..1680018 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMaxLongSubSequence.java @@ -0,0 +1,78 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; +import java.util.Comparator; + +public class DynamicMaxLongSubSequence { + + public static void main(String[] args) { + + String str1 = "1A2C3D4B56"; + + String str2 = "B1D23CA45B6A"; + + + String maxLongsubSequence = getMaxLongSubSequence(str1, str2); + + System.out.println(maxLongsubSequence); + } + + private static String getMaxLongSubSequence(String str1, String str2) { + + + char[] charArray1 = str1.toCharArray(); + char[] charArray2 = str2.toCharArray(); + + int[][] dp = new int[str1.length()][str2.length()]; + + dp[0][0] = (charArray1[0] == charArray2[0] ? 1 : 0); + + //填充第一行 + for (int i = 1; i < str1.length(); i++) { + int count = (charArray1[i] == charArray2[0] ? 1 : 0); + dp[i][0] = Math.max(dp[i - 1][0], count); + } + //填充第一列 + for (int j = 1; j < str2.length(); j++) { + int count = (charArray1[0] == charArray2[j] ? 1 : 0); + dp[0][j] = Math.max(dp[0][j - 1], count); + } + + for (int i = 1; i < str1.length(); i++) { + for (int j = 1; j < str2.length(); j++) { + //去除两边最大值 + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + if (charArray1[i] == charArray2[j]) { + //相等 并不是直接增加1 ,因为前面有可能增加过,所以要和前一行前一列的值+1比较 + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1); + } + } + } + + int m = str1.length() - 1; + int n = str2.length() - 1; + //获取子序列 + char[] res = new char[dp[str1.length() - 1][str2.length() - 1]]; + + int index = res.length - 1; + while (index >= 0) { + if (n > 0 && dp[m][n] == dp[m][n - 1]) { + n--; + } else if (m > 0 && dp[m][n] == dp[m - 1][n]) { + m--; + } else { + res[index--] = charArray1[m]; + m--; + n--; + } + } + return Arrays.toString(res); + } + +} + + + + + + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMaxLongSubString.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMaxLongSubString.java new file mode 100644 index 0000000..b2b8223 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMaxLongSubString.java @@ -0,0 +1,105 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; + +public class DynamicMaxLongSubString { + + public static void main(String[] args) { + + String str1 = "1AB2345CD"; + + String str2 = "12345EF"; + + + String maxLongsubSequence = getMaxLongSubString(str1, str2); + + System.out.println(maxLongsubSequence); + } + + /** + * 1 1 1 1 1 1 1 + * 1 1 1 1 1 1 1 + * 1 1 1 1 1 1 1 + * 1 1 1 1 1 1 1 + * 1 1 2 2 2 2 2 + * 1 1 2 3 3 3 3 + * 1 1 2 3 4 4 4 + * 1 1 2 3 4 4 4 + * 1 1 2 3 4 4 4 + * [2, 3, 4, 5] + * + * @param str1 + * @param str2 + * @return + */ + private static String getMaxLongSubString(String str1, String str2) { + + + char[] charArray1 = str1.toCharArray(); + char[] charArray2 = str2.toCharArray(); + + int[][] dp = new int[str1.length()][str2.length()]; + + dp[0][0] = (charArray1[0] == charArray2[0] ? 1 : 0); + + //填充第一行 + for (int i = 1; i < str1.length(); i++) { + int count = (charArray1[i] == charArray2[0] ? 1 : 0); + dp[i][0] = Math.max(dp[i - 1][0], count); + } + //填充第一列 + for (int j = 1; j < str2.length(); j++) { + int count = (charArray1[0] == charArray2[j] ? 1 : 0); + dp[0][j] = Math.max(dp[0][j - 1], count); + } + + for (int i = 1; i < str1.length(); i++) { + for (int j = 1; j < str2.length(); j++) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + if (charArray1[i] == charArray2[j] + && charArray1[i - 1] == charArray2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } + } + } + printArray(dp, str1.length(), str2.length()); + char resultArray[] = new char[dp[str1.length() - 1][str2.length() - 1]]; + int i = resultArray.length - 1; + + + for (int j = str1.length() - 1; j > 0; j--) { + int a = dp[j][str2.length() - 1]; + int b = dp[j - 1][str2.length() - 1]; + if (a > b) { + resultArray[i--] = charArray1[j]; + } + if (i == 0 && a <= b) { + resultArray[i--] = charArray1[j]; + } + if (i < 0) { + break; + } else { + continue; + } + } + + + return Arrays.toString(resultArray); + } + + + private static void printArray(int[][] m, int l1, int l2) { + for (int i = 0; i < l1; i++) { + for (int j = 0; j < l2; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } +} + + + + + + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMoney.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMoney.java new file mode 100644 index 0000000..b093feb --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMoney.java @@ -0,0 +1,200 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class DynamicMoney { + + public static void main(String[] args) { + Integer m[] = new Integer[]{5, 2, 3}; + + int mInt[] = new int[]{5, 2, 3}; + //由大到小排序,效率更高 + Arrays.sort(m, Collections.reverseOrder()); + +// int curMoney = 20;//4 +// int curMoney = 19; // 5 5 5 2 2 + int curMoney = 18; // 5 5 5 3 + +// int count = splitMoney(m, 0, curMoney); + int count = minConins3(mInt, curMoney); + System.out.println("count=" + count); + } + + /** + * 思路就是遍历逐个遍历,使用 0 张 1 ,2 张直到最大 + * + * @param m + * @param index + * @param curMoney + * @return + */ + private static int splitMoney(Integer[] m, int index, int curMoney) { + //都遍历完了,也得到了最小值 + if (curMoney == 0) { + return 0; + } + + /** + * 回溯当前Size + */ + int currentSize = -1; + /** + * 遍历每一个元素,从不选,到选择最大 + */ + for (int i = index; i < m.length; i++) { + if (curMoney >= m[i]) { + int maxSelect = curMoney / m[i];//最大选择 + //从0开始 j 就是选择张数 + for (int j = 0; j <= maxSelect; j++) { + int restMoney = curMoney - m[i] * j; + int next = splitMoney(m, i + 1, restMoney); + + if (next != -1) {//有解 + if (currentSize == -1) { + //一次没有被回溯直接赋值,这里是i 加一,不是index +1 + currentSize = j + next; + } else { + currentSize = Math.min(currentSize, j + next); + } + } else { + if (restMoney == 0) { + currentSize = j; + } + } + } + } + } + return currentSize; + } + + public int minCoins1(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return -1; + } + return process(arr, 0, aim); + } + + /** + * 优化两层循环合并成一层 ,因为循环不是每次都从0开始,后续的遍历不涉及到前面 + * + * @param arr + * @param i + * @param rest + * @return + */ + private int process(int[] arr, int i, int rest) { + if (i == arr.length) { + return rest == 0 ? 0 : -1; + } + + int count = -1; + + for (int k = 0; k * arr[i] <= rest; k++) { + int next = process(arr, i + 1, rest - k * arr[i]); + if (next != -1) { + count = count == -1 ? next + k : Math.min(count, next + k); + } + } + return count; + } + + + /** + * 动态规划最优实现方案 + * @param arr + * @param aim + * @return + */ + public static int minConins2(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return -1; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + //设置最后一排的值,除dp[N][0]外,其他都是-1 + + for (int col = 1; col <= aim; col++) { + dp[N][col] = -1; + } + for (int i = N - 1; i >= 0; i--) {//从底向上计算每一行 + for (int rest = 0; rest <= aim; rest++) { + dp[i][rest] = -1;//初始时先设置dp[i][rest]的值无效 + if (dp[i + 1][rest] != -1) {//下面的值如果有效 + dp[i][rest] = dp[i + 1][rest];//先设置成下面的值 + } + if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1) { + if (dp[i][rest] == -1) { + dp[i][rest] = dp[i][rest - arr[i]] + 1; + } else { + dp[i][rest] = Math.min(dp[i][rest], dp[i][rest - arr[i]] + 1); + } + } + } + + } + printArray(dp); + return dp[0][aim]; + } + + /** + * 动态规划普通方案 + * @param arr + * @param aim + * @return + */ + public static int minConins3(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return -1; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + //设置最后一排的值,除dp[N][0]外,其他都是-1 + + //最后一排为溢出所以都为-1无解 + for (int col = 1; col <= aim; col++) { + dp[N][col] = -1; + } + for (int i = N - 1; i >= 0; i--) {//从底向上计算每一行 + for (int rest = 0; rest <= aim; rest++) { + singleDataCalculate(arr[i], dp, i, rest); + } + } + printArray(dp);//测试打印 + return dp[0][aim]; + } + + //这个可以优化 + //把所有等式列出来发现 dp[i][rest] = Math.min(dp[i][rest], dp[i][rest - arr[i]] + 1); + private static void singleDataCalculate(int i, int[][] dp, int i2, int rest) { + dp[i2][rest] = -1;//初始时先设置dp[i][rest]的值无效 + int k = rest / i2;//可试的最大张数 + if (rest == 0) {//如果0表示不需要找钱 也就需要0张 + dp[i2][rest] = 0; + } else { + int minValue = -1;//记录最小值 + for (int j = 0; j <= k; j++) { + int next = dp[i2 + 1][rest - j * i2]; + if (next != -1) {//-1表示无解不记录统计 + if (minValue == -1) {//第一次统计,无需比较直接赋值 + minValue = next + j; + } else { + minValue = Math.min(minValue, next + j); + } + } + } + dp[i2][rest] = minValue; + } + } + + private static void printArray(int[][] m) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 19; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMoneyWays.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMoneyWays.java new file mode 100644 index 0000000..3137f73 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicMoneyWays.java @@ -0,0 +1,100 @@ +package com.wangpos.datastructure.algorithm; + +public class DynamicMoneyWays { + + public static void main(String[] args) { + + int arr[] = new int[]{5, 1}; + + int aim = 15; + /*** + * 5 + * 55 + * 555 + * 11111111 + * + * 10 5 + * 10 1 1 1 1 1 + * + */ +// int aim = 1000; + +// int result = coin(arr, 0, aim); + int result = coin2(arr, aim); + System.out.println(result); + } + + private static int coin(int[] arr, int index, int aim) { + if (index == arr.length) { + return 0; + } + int count = 0; + int maxUse = 0; + //找到小于当前面值的元素 + for (int i = index; i < arr.length; i++) { + maxUse = aim / arr[i]; + if (maxUse != 0) { + index = i; + break; + } + } + + if (maxUse != 0) {//表示没有越界 + for (int j = 0; j <= maxUse; j++) { + int rest = aim - j * arr[index]; + if (rest == 0) {//有解 + count += 1; + } else {//无解,遍历下一个 + int next = coin(arr, index + 1, rest); + if (next != 0) {//等于0无解 + count += next; + } + } + } + } + return count; + } + + /** + * 动态规划方法 + * @param arr + * @param aim + * @return + */ + private static int coin2(int[] arr, int aim) { + int dp[][] = new int[arr.length + 1][aim + 1]; + for (int i = 0; i <= arr.length; i++) { + dp[i][0] = 1; + } + + int N = arr.length - 1; + for (int i = N; i >= 0; i--) { + for (int j = 1; j <= aim; j++) { + int maxValue = j / arr[i]; + if (maxValue == 0) { + continue;//无效 + } + int count = 0; + for (int k = 0; k <= maxValue; k++) { + int before = dp[i + 1][j - k * arr[i]]; + count += before; + } + dp[i][j] = count; + + } + } + + printArray(dp); + return dp[0][aim]; + } + + + private static void printArray(int[][] m) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 16; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicNestLetter.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicNestLetter.java new file mode 100644 index 0000000..e68f0f9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicNestLetter.java @@ -0,0 +1,92 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; +import java.util.Comparator; + +public class DynamicNestLetter { + + public static void main(String[] args) { + int arr[][] = new int[][]{{3, 4}, {2, 3}, {4, 5}, {1, 3}, {2, 2}, {3, 6}, {1, 2}, {3, 2}, {2, 4}}; + // int arr[][] = new int[][]{{3, 4}, {2, 3}, {4, 5}, {1, 3}, {2, 2}, {3, 6}}; + + int result = calculateTwoSplit(getSortedEnvelopes(arr)); + System.out.println(result); + } + + /** + * 将数组按照按照长度排序,然后求宽度的最长增长子串 + * + * 。为什么呢?这与我们的排序策略有关,按照长度从小到大排序,长度相等的信封之间按照宽度从大到小排序。 + * + * @param arr + * @return + */ + private static int calculateTwoSplit(Envelope[] arr) { + + //存储第i个位置结尾,最长递增子串长度 + + //根据最后一个dp 值向前遍历,找打小于他的一个值,并且dp[i] = dp[7]-1 + + int dp[] = new int[arr.length]; + //有效值 + int ends[] = new int[arr.length]; + ends[0] = arr[0].wid; + dp[0] = 1; + // 0 right有效区 + int right = 0; + for (int i = 1; i < arr.length; i++) { + int l = 0; + int r = right; + while (l <= r) { + int mid = (l + r) / 2; + if (arr[i].wid > ends[mid]) { + l = mid + 1; + } else { + r = mid - 1; + } + } + if (l > right) {//有效区扩张 + right = l; + } + ends[l] = arr[i].wid; + dp[i] = l + 1; + + } + + return right + 1; + + } + + //这个排序数组,我们长度是按小到大,所以只需要看宽度的递增子序列即可 + public static Envelope[] getSortedEnvelopes(int[][] matrix) { + Envelope[] res = new Envelope[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + res[i] = new Envelope(matrix[i][0], matrix[i][1]); + } + Arrays.sort(res, new EnvelopeComparator()); + return res; + } + +} + +class Envelope { + public int len; + public int wid; + + public Envelope(int weight, int hight) { + len = weight; + wid = hight; + } +} + +class EnvelopeComparator implements Comparator { + @Override + public int compare(Envelope o1, Envelope o2) { + return o1.len != o2.len ? o1.len - o2.len : o2.wid - o1.wid; + } +} + + + + + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgram.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgram.kt new file mode 100644 index 0000000..b25a663 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgram.kt @@ -0,0 +1,145 @@ +package com.wangpos.datastructure.algorithm + +import android.os.Build.VERSION_CODES.N +import kotlin.math.max +import android.icu.lang.UCharacter.GraphemeClusterBreak.V + + +/** + * 动态规划算法,是解决多阶段决策问题常用的最优化理论 + * + * + * 1. 定义最优子问题 + * + * 2. 定义状态 + * + * 3. 定义决策和状态转换方程 + * + * 4. 确定边界条件 + * + * 01 背包问题,用动态规划法 + * + * 给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。 + * + * 应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大? + * + * 分析一波,面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。 + * + * 声明一个 大小为 m[n][c] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为 j 时所能获得的最大价值 + * + * 那么我们可以很容易分析得出 m[i][j] 的计算方法, + * + * 1). j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿 所以价值还是和上次,只不过容量变大了,但也是当前背包容量的最大值 + * (这个当前容量最大值得意思,只对当前放的这个些物品来讲,比如后面有价值更大的,我们不在本次考虑范围,本次考虑就是放当前物品时背包的最大价值, + * 同理,当放入第二件物品时,我们就考虑两件物品时当前背包的最大值,我们会一次把所有情况,按照放入物品数量的增加,和背包容量的增加,一次获取最大价值 + * ) + * m[ i ][ j ] = m[ i-1 ][ j ] (m[i-1][j]表示容量增加了,但是不拿物品的价值,对应表正的上一行数据,也是之前算好了) + * + * + * 2). j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。 + +如果拿取,m[ i ][ j ]= m[ i-1 ][ j-w[ i ] ] + v[ i ]。 + +注意这里解释一下,当前物品数量和背包容量对应的最大值 = 没放入该物品前数量和没放入物品前的容量的最大值 + 当前物品价值 + +(所以没放入物品前数量对应i-1, +而容量对应j-w[i],就是当前可以容纳容量j减去当前要放入物品的容量,二对一这个值,在前面的求解当中已经求解,所以,我们就能推倒新的解 ) + +这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。 + +如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] + +究竟是拿还是不拿,自然是比较这两种情况那种价值最大。 + +注意,当我们拿第二件物品的时候,通过这个算法是存在替换的第一件物品的情况,之前一直不理解,以为很顺序有关,第一件放进去了,就一直在里面,后面的累加,实际不是这样 + +m[ i-1 ][ j-w[ i ] ] 就是会根据当前容量去寻找没装前的容量的最大值 然后再加上自身的价值在去进行补缴 + + * + * + * + * 价值数组v = {8, 10, 6, 3, 7, 2}, + +重量数组w = {4, 6, 2, 2, 5, 1}, + +背包容量C = 12 时对应的m[i][j]数组 + + */ + +/** + * 注意,我们再数组前添加一个零,这样我们就可以从第一个处理了,第一个处理的实际是第二个数据 + */ +fun main(arg: Array) { + var N = 15 + + var v = arrayOf(0, 8, 10, 6, 3, 7, 2) + var w = arrayOf(0, 4, 6, 2, 2, 5, 1) + + var n = 7 + var c = 13 + + var m = Array(7) { IntArray(13) } + + dynamicBag(w, v, n, c, m) + + // 1 2 3 + +} + +/** + *  k) 到此,01背包问题已经解决,利用动态规划解决此问题的效率即是填写此张表的效率, + * 所以动态规划的时间效率为O(number*capacity)=O(n*c),由于用到二维数组存储子问题的解,所以动态规划的空间效率为O(n*c); + */ + +fun dynamicBag(w: Array, v: Array, n: Int, c: Int, m: Array) { + + for (i in 1 until n) { + for (j in 1 until c) { + if (j >= w[i]) { + m[i][j] = max(m[i - 1][j], m[i - 1][j - w[i]] + v[i]) + } else { + m[i][j] = m[i - 1][j]; + } + } + } + + + for (i in 1 until n) { + for (j in 1 until c) { + print(" ${m[i][j]} ") + } + println() + } + + var x = arrayOf(0, 0, 0, 0, 0, 0, 0) + + println("查找哪些放入背包了") + findData(x, w, v, n - 1, c - 1, m) + + for (i in 0 until x.size) { + if (x[i] == 1) { + println(i) + } + } +} + +/** + * 另起一个 x[ ] 数组,x[i]=0表示不拿,x[i]=1表示拿。 + +m[n][c]为最优值,如果m[n][c]=m[n-1][c] ,说明有没有第n件物品都一样,则x[n]=0 ; +否则 x[n]=1。当x[n]=0时,由x[n-1][c]继续构造最优解; +当x[n]=1时,则由x[n-1][c-w[i]]继续构造最优解。以此类推,可构造出所有的最优解。 + * + */ + +fun findData(x: Array, w: Array, v: Array, n: Int, c: Int, m: Array) { + if (n >= 1) { + if (m[n][c] == m[n - 1][c]) { + x[n] = 0 + findData(x, w, v, n - 1, c, m) + } else if (c - w[n] >= 0 && m[n][c] == m[n - 1][c - w[n]] + v[n]) { + x[n] = 1 + findData(x, w, v, n - 1, c - w[n], m) + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgramWorkStastion.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgramWorkStastion.kt new file mode 100644 index 0000000..64dea25 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicProgramWorkStastion.kt @@ -0,0 +1,52 @@ +package com.wangpos.datastructure.algorithm + + +/** + * 动态规划首先确定子问题,也就是决策问题 + */ +fun main(arg: Array) { + + // 第一条线装配站 + var station1 = arrayOf(1, 10, 6, 3, 7, 2) + // 第二条线装配站 + var station2 = arrayOf(4, 6, 2, 2, 5, 1) + + var time = Array(6) { IntArray(2) } + + dynamic(station1, station2, time) + +} + +fun dynamic(station1: Array, station2: Array, time: Array) { + for (i in 0..5) { + if (station1[i] < station2[i]) { + time[i][0] = station1[i] + } else { + time[i][1] = station2[i] + } + } + + findSmallTimes(time) +} + +fun findSmallTimes(time: Array) { + time.forEach { + if (it[0] > 0) { + println("one ${it[0]}") + } else { + println("two ${it[1]}") + } + } + +} + + + +/** +用动态规划法设计算法的关键是找出子问题和子问题状态的定义。 +子问题的状态就是子问题的最优解,当子问题的规模是最终的问题的时候, +那么其对应的状态就是问题的最优解。基于这一思想,通常选择把问题的规模作为状态变量的方式定义子问题。 +以取硬币问题为例,其问题是取 N 元硬币需要的最小硬币数量。 +于是我们就选择“取 i(0 ≤ i ≤ N)元硬币需要的最小硬币数量”作为子问题, +并定义状态 d[i] 为取 i 元硬币需要的最小硬币数量。 + */ \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicRobert.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicRobert.java new file mode 100644 index 0000000..9999c2d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicRobert.java @@ -0,0 +1,81 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class DynamicRobert { + + private static int N = 7; + private static int K = 10;//这里步骤要+1 + private static int P = 4;//这里数组-1 + + public static void main(String[] args) { + + int dp[][] = new int[K][N]; + + int result = way(dp); + + System.out.println("result="+result); + + printArray(dp); + } + + private static int way(int[][] dp) { + dp[0][P] = 1; + for (int i = 1; i < K; i++) { + dp[i][0] = dp[i][0] + dp[i - 1][1]; + dp[i][N - 1] = dp[i][N - 1] + dp[i - 1][N - 2]; + for (int j = 1; j < N - 1; j++) { + dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1]; + } + } + + return dp[9][3]; + } + + + private static void printArray(int[][] m) { + for (int i = 0; i < K; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + + } + + public int ways1(int N, int M, int K, int P) { + if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) { + return 0; + } + return walk(N, M, K, P); + } + + + /** + * @param N 位置1~N + * @param cur 当前位置 可变参数 + * @param rest 剩余步骤 可变参数 + * @param P 最终目标位置P + * @return 表示解法数量 + */ + public int walk(int N, int cur, int rest, int P) { + + if (rest == 0) { + return cur == P ? 1 : 0; + } + + if (cur == 1) { + return walk(N, 2, rest - 1, P); + } + + if (cur == N) { + return walk(N, N - 1, rest - 1, P); + } + + return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P); + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicShootBalloon.java b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicShootBalloon.java new file mode 100644 index 0000000..f600b7b --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/DynamicShootBalloon.java @@ -0,0 +1,33 @@ +package com.wangpos.datastructure.algorithm; + +public class DynamicShootBalloon { + + public static void main(String[] args) { + + int[] arr = new int[]{3, 2, 5}; + +// int result = shoot(arr, 0, 1); + + + } + +// private static int shoot(int[] arr, int l, int r) { +// +// int score = 0; +// if (l >= 0) { +// int cv = arr[l]; +// int lv = 1; +// int rv = 1; +// if (l - 1 >= 0) { +// lv = arr[l - 1]; +// } +// if (r < arr.length) { +// rv = arr[r]; +// } +// score = lv * cv * rv; +// } +// +// +// return 0 +// } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy.java b/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy.java new file mode 100644 index 0000000..fc69d35 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy.java @@ -0,0 +1,128 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 找零是一个很常见的问题,平时找钱为了都会考虑尽可能找少的数量的钱给用户,顾客也不希望自己得很多零钱 + * 这是一个典型的使用贪婪算法的题 + *

+ * 通过币种的问题我们发现,贪心算法每次都要求选择最优的,所以,最终结果并不一定是最优解, + *

+ * 硬币种类 + * [[25, 20, 10, 5, 1]] + * 应找给对方的硬币 数量 :4 + * [[25, 10, 5, 1]] + *

+ *

+ * 硬币种类 + * [[25, 20, 5, 1]] + * 应找给对方的硬币 数量 :5 + * [[25, 5, 5, 5, 1]] 实际最优 是 20 20 1 + *

+ * 实际顾客需要的是20 20 1 还是 25 5 5 5 1呢,答案是第二个,因为他心理更想留大面值币种 + *

+ *

+ * 实际场景 顾客 希望的是尽量给我整意思就是 最大的面值的钱先给我找,也就是贪心算法 + *

+ * 贪心算法效率高,找钱就是需要效率高的场景,但影响最终找的金额数,所以 + *

+ * 贪心算法可以作为其他最优解算法中的辅助算法 + */ +public class Greedy { + static List coins = new ArrayList(); + + public static void main(String[] args) { + // 币种是 25 20 10 5 1 +// coins.add(25); +// coins.add(20); +// coins.add(10); +// coins.add(5); +// coins.add(1); + + // 币种是 25 20 5 1 + coins.add(25); + coins.add(20); + coins.add(5); + coins.add(1); + + System.out.println("硬币种类"); + System.out.println(Arrays.asList(coins)); + + int target = 41; + Result result = getLeastCoins(target); + System.out.println("应找给对方的硬币 数量 :" + result.datas.size()); + System.out.println(Arrays.asList(getLeastCoins(target).datas)); + + System.out.println(Arrays.asList(getLeastNumberCoins(target).datas)); + } + + private static Result getLeastCoins(int target) { + Result result = new Result(); + result.datas = new ArrayList<>(); + //已知coins是有序的 + for (Integer coin : coins) { + if (target >= coin) { + // 求个数 + int count = target / coin; + for (int i = 0; i < count; i++) { + result.datas.add(coin); + } + // 取余数 + target = target % coin; + } + } + return result; + } + + /** + * 可以采用局部最优解法加排序算法,找到最优解 + * @param target + * @return + */ + + private static Result getLeastNumberCoins(int target) { + Result minNumberResult = new Result(); + + for (int i = 0; i < coins.size(); i++) { + Result current = getLeastCoinsByList(i, target); + if (minNumberResult.datas != null) { + if (minNumberResult.getCount() > current.getCount()) { + minNumberResult.datas = current.datas; + }else{ + minNumberResult.datas = current.datas; + } + } + } + + return minNumberResult; + } + + private static Result getLeastCoinsByList(int i, int target) { + Result result = new Result(); + result.datas = new ArrayList<>(); + //已知coins是有序的 + for (int j = i; j < coins.size(); j++) { + int coin = coins.get(i); + if (target >= coin) { + // 求个数 + int count = target / coin; + for (int m = 0; m < count; m++) { + result.datas.add(coin); + } + // 取余数 + target = target % coin; + } + } + return result; + } + + static class Result { + public List datas = null; + + public int getCount() { + return datas.size(); + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy01Bag.java b/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy01Bag.java new file mode 100644 index 0000000..76b66e8 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/Greedy01Bag.java @@ -0,0 +1,179 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +/** + * 贪心算法求解01 背包问题 + *

+ * 假设背包最多承载的重量是150 ,不考虑体积的情况 + *

+ * 现在有 重量 wi = [ 35,30,60,50,40,10,25] 7个物品 + * 价值 pi = [10,40,30,50,35,40,30] + *

+ *

+ * 按价值贪婪算法 + * 总重量=130 + * 总价值=165 + *

+ * 按最轻贪婪算法 + * 总重量=140 + * 总价值=155 + * + * 按价值密度贪婪算法 + * 总重量=150 + * 总价值=170 + * + * + * 最终都得不到最优解,最优解需要动态规划法来解决 + */ + + +public class Greedy01Bag { + + public static void main(String[] args) { + + // 确定子问题 + + // 贪婪策略有3种, + // 第一种给句物品价值选择,每次都选择价值最高的物品 + + int[] pi = {10, 40, 30, 50, 35, 40, 30}; + List things = new ArrayList<>(); + things.add(new Thing(35, 10, 0)); + things.add(new Thing(30, 40, 0)); + things.add(new Thing(60, 30, 0)); + things.add(new Thing(50, 50, 0)); + things.add(new Thing(40, 35, 0)); + things.add(new Thing(10, 40, 0)); + things.add(new Thing(25, 30, 0)); + + Bag bag = getMAX(things); + System.out.println("按价值使用贪婪算法"); + System.out.println(Arrays.asList(bag.datas)); + + System.out.println("总重量=" + bag.getTotalWeight()); + System.out.println("总价值=" + bag.getTotalValue()); + } + + + private static Bag getMAX(List things) { + Bag bag = new Bag(150); + bag.datas = new ArrayList<>(); + for (int i = 0; i < things.size(); i++) { +// Thing thing = getMaxValueThing(things); +// Thing thing = getMinWeightThing(things); + Thing thing = getMaxValueDensity(things); + if (thing != null && thing.status == 0) { + if (bag.weight >= thing.weight) { + thing.status = 1; + bag.datas.add(thing); + bag.weight = bag.weight - thing.weight; + } else { + thing.status = 2; + } + } + } + + return bag; + } + + // 第一种给句物品价值选择,每次都选择价值最高的物品 + private static Thing getMaxValueThing(List things) { + Thing maxThing = null; + for (Thing thing : things) { + if (thing.status == 0) { + if (maxThing == null) { + maxThing = thing; + } else if (maxThing.value < thing.value) { + maxThing = thing; + } + } + } + return maxThing; + } + + // 第二种给句物品价值选择,每次都选择最轻物品 + private static Thing getMinWeightThing(List things) { + Thing maxThing = null; + for (Thing thing : things) { + if (thing.status == 0) { + if (maxThing == null) { + maxThing = thing; + } else if (maxThing.weight > thing.weight) { + maxThing = thing; + } + } + } + return maxThing; + } + + // 第三种给句物品价值选择,每次都价值密度大的 + private static Thing getMaxValueDensity(List things) { + Thing maxThing = null; + for (Thing thing : things) { + if (thing.status == 0) { + if (maxThing == null) { + maxThing = thing; + } else if ((maxThing.value*1f / maxThing.weight) < (thing.value*1f / thing.weight)) { + maxThing = thing; + } + } + } + return maxThing; + } + + + static class Bag { + public Bag(int weight) { + this.weight = weight; + } + + List datas; + int weight; + + public int getTotalWeight() { + int totalW = 0; + for (Thing data : datas) { + totalW = totalW + data.weight; + } + return totalW; + } + + public int getTotalValue() { + + int totalV = 0; + for (Thing data : datas) { + totalV = totalV + data.value; + } + return totalV; + } + } + + static class Thing { + @Override + public String toString() { + return "Thing{" + + "weight=" + weight + + ", value=" + value + + ", status=" + status + + '}'; + } + + public Thing(int weiget, int value, int status) { + this.weight = weiget; + this.value = value; + this.status = status; + } + + int weight; + int value; + int status;// 0未选中,1选中,2不合适 + + } + +} + + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/IterationGCD.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/IterationGCD.kt new file mode 100644 index 0000000..c293f52 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/IterationGCD.kt @@ -0,0 +1,76 @@ +package com.wangpos.datastructure.algorithm + + +/** + * 迭代算法又称辗转法,一般求解数学问题,如一元高次方程,线性和非线性方程和曲线拟合等问题,一般用来求出解或者近似解 + * + * 1. 确定迭代变量 + * + * 2. 确定迭代递推关系 + * + * 3. 确定迭代终止条件 + * + * + * + * + * 欧几里得算法,两个整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数 + * + * 所以拿到较小的数和余数再次进行这条规则,直到余数为0,返回除数 + * + * Greatest Common Divisor 最大公约数 + */ + + +fun main(args: Array) { + + var a = 26 + var b = 8 + + println("求解 $a 和 $b 的最大公约数") + + println("迭代算法的实现;" + getGCD(a, b)) + + println("递归算法的实现;" + getGCDByRecursion(a, b)) + +} + +/** + * 欧几里得 辗转相除法 + */ +fun getGCD(first: Int, second: Int): Int? { + + var a = first + var b = second + + if (a < b) { + var temp = a + a = b + b = a + } + + while (b > 0) {//终止条件除数为0 + var c = a % b + a = b + b = c + } + + return a +} + +fun getGCDByRecursion(first: Int, second: Int): Int { + + var a = first + var b = second + if (a < b) { + var temp = a + a = b + b = a + } + var c = a % b + + if (c == 0) { + return b + } else { + return getGCDByRecursion(b, c) + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/NEmpress.java b/app/src/main/java/com/wangpos/datastructure/algorithm/NEmpress.java new file mode 100644 index 0000000..5597c9c --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/NEmpress.java @@ -0,0 +1,69 @@ +package com.wangpos.datastructure.algorithm; + +public class NEmpress { + + public static void main(String[] args) { + // 4 == 2 + // 1 == 1 + // 2 3 == 0 + + // 8 ==92 + Queen queen = new Queen(5); + queen.backtrack(1); + } + +} + + + class Queen { + private int[] column; //同栏是否有皇后,1表示有 + private int[] rup; //右上至左下是否有皇后 + private int[] lup; //左上至右下是否有皇后 + private int[] queen; //解答 + private int num; //解答编号 + private int n = 1; + + public Queen(int N) { + this.n = N; + column = new int[n+1]; + rup = new int[(2*n)+1]; + lup = new int[(2*n)+1]; + for (int i = 1; i <= n; i++) + column[i] = 0; + for (int i = 1; i <= (2*n); i++) + rup[i] = lup[i] = 0; //初始定义全部无皇后 + queen = new int[n+1]; + } + + public void backtrack(int i) { + if (i > n) { + showAnswer(); + } else { + for (int j = 1; j <= n; j++) { + if ((column[j] == 0) && (rup[i+j] == 0) && (lup[i-j+n] == 0)) { + //若无皇后 + queen[i] = j; //设定为占用 + column[j] = rup[i+j] = lup[i-j+n] = 1; + backtrack(i+1); //循环调用 + column[j] = rup[i+j] = lup[i-j+n] = 0; + } + } + } + } + + protected void showAnswer() { + num++; + System.out.println("\n解答" + num); + for (int y = 1; y <= n; y++) { + for (int x = 1; x <= n; x++) { + if(queen[y]==x) { + System.out.print("Q"); + } else { + System.out.print("."); + } + } + System.out.println(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/QuickSort.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/QuickSort.kt new file mode 100644 index 0000000..64b2856 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/QuickSort.kt @@ -0,0 +1,51 @@ +package com.wangpos.datastructure.algorithm + +import android.util.Log +import java.util.* + +fun main(args: Array) { + + var arrays = arrayOf(2, 21, 13, 45, 55, 36, 17, 18, 99, 10) + + println("待排序的队列 ${arrays.toList()}") + quickSort(arrays, 0, arrays.size-1) + println("已排序的队列 ${arrays.toList()}") + +} + +fun quickSort(arrays: Array, start: Int, end: Int) { + if (start >= end) { + return + } + var flagIndex = quickUnitSort(arrays, start, end) + quickSort(arrays, start, flagIndex - 1) + quickSort(arrays, flagIndex + 1, end) +} + +fun quickUnitSort(arrays: Array, start: Int, end: Int): Int { + var low = start + var high = end + var key = arrays[low] + while (low < high) { + while (arrays[high] >= key && low < high) { + --high + } + arrays[low] = arrays[high] + while (arrays[low] <= key && low < high) { + ++low + } + arrays[high] = arrays[low] + } + var meetPosition: Int = low + arrays[meetPosition] = key + return meetPosition + +} + + +fun swap(arrays: Array, i: Int, start: Int) { + var temp = arrays[start] + arrays[start] = arrays[i] + arrays[i] = temp +} + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringAnagram.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringAnagram.java new file mode 100644 index 0000000..418cba9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringAnagram.java @@ -0,0 +1,56 @@ +package com.wangpos.datastructure.algorithm; + +public class StringAnagram { + + public static void main(String[] args) { + String targetString1 = "abcee"; + String targetString2 = "bcdea"; + + boolean result = isDeformation(targetString1, targetString2); + + System.out.println("结果:" + result); + + } + + /** + * 为什么要创建256个长度数组 + *

+ * ASCII编码表中一共有256个字符 + *

+ * 前128个为常用的字符 如 运算符 字母 数字等 键盘上可以显示的 + * 后 128个为 特殊字符 是键盘上找不到的字符 + * + * // 为什么有小于0 就是false, + * //首先前提我们判断过是长度相等的 + * //如果chas1 中种类多和chas1种类数量不一样,就一定会出现负数, + * //出现负数有两种,第一种是种类多的变为负数,第二种是没有的变为负数 + * @param targetString1 + * @param targetString2 + * @return + */ + private static boolean isDeformation(String targetString1, + String targetString2) { + if (targetString1 == null || targetString2 == null + || targetString1.length() != targetString2.length() + ) { + return false; + } + + char[] chas1 = targetString1.toCharArray(); + char[] chars2 = targetString2.toCharArray(); + // + int[] map = new int[256]; + + for (char c : chas1) { + map[c]++; + } + for (char c2 : chars2) { + map[c2]--; + if (map[c2] < 0) { + return false; + } + } + + return true; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringArraySearchString.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringArraySearchString.java new file mode 100644 index 0000000..ec7f83b --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringArraySearchString.java @@ -0,0 +1,51 @@ +package com.wangpos.datastructure.algorithm; + +public class StringArraySearchString { + + public static void main(String args[]) { + String str = "a"; + + String[] strArrays = new String[]{null, "a", null, "a", null, "b", null, "c"}; + + System.out.println(getIndex(strArrays, str)); + } + + //二分法查找, + private static int getIndex(String[] strs, String str) { + if (strs == null || strs.length == 0 || str == null) { + return -1; + } + int res = -1; + int left = 0; + int right = strs.length - 1; + int mid = 0; + int i = 0; + + while (left < right) { + mid = (left + right) / 2; + if (strs[mid] != null && strs[mid].equals(str)) { + res = mid;//返回最左边,所以要继续遍历 + right = mid - 1; + } else if (strs[mid] != null) { + if (strs[mid].compareTo(str) < 0) { + left = mid + 1; + } else { + right = mid - 1; + } + } else { + i = mid; + while (strs[i] == null && --i >= left) ; + + if (i < left || strs[i].compareTo(str) < 0) { + left = mid + 1; + } else { + res = strs[i].equals(str) ? i : res; + right = i - 1; + } + } + } + + + return res; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringBracketStringInValid.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringBracketStringInValid.java new file mode 100644 index 0000000..516f8cd --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringBracketStringInValid.java @@ -0,0 +1,79 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; +import java.util.Stack; + +public class StringBracketStringInValid { + + public static void main(String[] args) { + + String str = "()()()"; + String str2 = "((()()(()("; + String str3 = "121()()()"; + System.out.println("result=" + inValidBracketString(str)); + System.out.println("result=" + inValidBracketString(str3)); + + String str4 = "())"; + + String str5 = "()(()()("; + + System.out.println(maxLengthBrcketString(str4)); + + System.out.println(maxLengthBrcketString(str5)); + } + + public static boolean inValidBracketString(String origin) { + char[] arrays = origin.toCharArray(); + Stack stack = new Stack(); + + for (char data : arrays) { + if (data != '(' && data != ')') { + return false; + } + if (stack.isEmpty()) { + stack.push(data); + } else { + char top = stack.peek(); + if (top == '(' && data == ')') { + stack.pop(); + } else { + stack.push(data); + } + } + } + + if (stack.isEmpty()) { + return true; + } + return false; + } + + + public static int maxLengthBrcketString(String origin) { + char[] arrays = origin.toCharArray(); + int length = arrays.length; + int dp[] = new int[length]; + //dp的值代表以当前结尾最长有效子串长度,什么意思? 比如()( dp[0]=0 dp[1]=2 dp[2] = 0 + //(这里我会有疑问,我们以为dp[2]= 2 其实不是 + // (() dp[2] = 2 如果结尾是) 从后向前找有效数量 + // ()(() dp[4] = 2 从后向前找到 + //()(()() dp[6] = 4 pre = 5 pre-1 = 4 dp[pre-1] = 2 + //()()) dp[4] = 0 + dp[0] = 0; + int pre = 0; + int res = 0; + for (int i = 1; i < arrays.length; i++) { + if (arrays[i] == ')') { + pre = i - dp[i - 1] - 1; + if (pre >= 0 && arrays[pre] == '(') { + //()(()()) + dp[i] = dp[i - 1] + 2 + (pre > 0 ? dp[pre - 1] : 0); + } + } + res = Math.max(res, dp[i]); + } + System.out.println(Arrays.toString(dp)); + return res; + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringContactMinDictionaryRank.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringContactMinDictionaryRank.java new file mode 100644 index 0000000..eee0241 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringContactMinDictionaryRank.java @@ -0,0 +1,37 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; +import java.util.Comparator; + + +/** + * 排序比较就可以了,重要的是排序条件 + */ +public class StringContactMinDictionaryRank { + + public static void main(String[] args) { + String[] strArray = new String[]{"abc", "bcd", "acd"}; + + System.out.println(lowString(strArray)); + + } + + public static class MyComparator implements Comparator{ + @Override + public int compare(String o1, String o2) { + return (o1+02).compareTo(o2+o1); + } + } + public static String lowString(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + + Arrays.sort(strs, new MyComparator()); + String res = ""; + for (int i = 0; i < strs.length; i++) { + res += strs[i]; + } + return res; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringFormulaCalculate.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringFormulaCalculate.java new file mode 100644 index 0000000..9894ef4 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringFormulaCalculate.java @@ -0,0 +1,213 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * 支持整数加减乘除公式 + */ +public class StringFormulaCalculate { + + public static void main(String[] args) { + /** + * 加减法计算 + */ + String formula = "1+2-1+5-6"; + /** + * 加减乘 + */ + String formula1 = "1+2*3"; //7 + String formula2 = "1+2*3+4"; //11 +// String formula3 = "1*2+3+4"; //9 + String formula4 = "1-2+3*4+6/2";//14 + + String formula5 = "2*(4-2)+5"; + + String formula6 = "2*(4-2)"; + + String formula7 = "(4-2)*2"; + String formula8 = "10*((5-2)-2)"; + String formula9 = "48*((70-65)-43)+8*1"; +// String formula = "48*((70-65)-43)+8*1"; +// String formula = "48*((70-65)-43)+8*1"; + +// calculate(formula1, 0); +// calculate(formula2, 0); +// calculate(formula3, 0); +// calculate(formula4,0); + calculate(formula9, 0); + Deque queues2 = queues; + System.out.println("结果" + queues); + } + + /** + * 存储 数字和运算符 + */ + private static Deque queues = new LinkedList<>(); + + private static void addLastNum(Integer cur) { + String curNum = cur.toString(); + if (queues.size() > 1) { + String sign = queues.removeLast(); + String prev = queues.removeLast(); + int result = calculateExpress(prev, curNum, sign); + queues.add(String.valueOf(result)); + } else { + queues.add(curNum); + } + if (queues.size() > 1) { + String last = queues.removeLast(); + addLastNum(Integer.valueOf(last)); + } + } + + /** + * 新加元素进行判断,如果前面是* / 直接运算,然后存入 + * 如果+ - 暂时不运算 + */ + private static void addNum(Integer cur) { + String curNum = cur.toString(); + String sign = queues.peekLast(); + if ("*".equals(sign) || "/".equals(sign)) { + queues.removeLast(); + String lastNum = queues.pollLast(); + int result = calculateExpress(lastNum, curNum, sign); + queues.add(String.valueOf(result)); + } else if ("+".equals(sign) || "-".equals(sign)) { + queues.add(curNum); + } else { + queues.add(curNum); + } + } + + /** + * 如果+ - ,前两个有值运算前两个 + * 否者直接加入 + * + * @param sign + */ + private static Integer addSign(char sign) { + if (sign == '+' || sign == '-' || sign == '(') { + String last = queues.peekLast(); + if ("(".equals(last) || queues.size() < 2) { + } else if (queues.size() > 2) { + String prev1 = queues.removeLast(); + String s = queues.removeLast(); + String prev3 = queues.removeLast(); + if ("(".equals(prev1)||"(".equals(s)||"(".equals(prev3)){ + queues.add(prev3); + queues.add(s); + queues.add(prev1); + }else { + int result = calculateExpress(prev3, prev1, s); + queues.add(String.valueOf(result)); + } + } + queues.add(String.valueOf(sign)); + } else if (sign == '*' || sign == '/') { + queues.add(String.valueOf(sign)); + } + + if (sign == ')') { + String prev1 = queues.removeLast(); + String s = queues.removeLast(); + String prev3 = queues.removeLast(); + int result = calculateExpress(prev3, prev1, s); + queues.removeLast();//移除左括号 + queues.add(String.valueOf(result)); + return result; + } + return 0; + } + + /** + * 结果表示 遍历到哪一位,和计算结果 + * + * @param formula + * @param + * @return + */ + private static int[] calculate(String formula, int index) { + + int s1 = 0; + + char[] f = formula.toCharArray(); + for (int i = index; i < formula.length(); i++) { + if (isNumber(f[i])) { + //转数字 + s1 = s1 * 10 + f[i] - '0'; + + } else if (isSign(f[i])) { + // 运算符前一定有数字 + if(s1>0) { + addNum(s1); + s1 = 0; + } + addSign(f[i]); + } else if (f[i] == '(') { + addSign(f[i]); + int result[] = calculate(formula, i + 1); + i = result[1]; + //记录回溯的位置,因为后面还可能有元素要处理 + //比如 1 + 2 *( 3 + 4 )+ 5 + } else if (f[i] == ')') { + addNum(s1); + int result = addSign(f[i]); + s1 = 0; + return new int[]{result, i}; + } + + } + /** + * 综合计算 + */ + if (s1 != 0) { + addLastNum(s1); + s1 = 0; + }else{ + if(queues.size()>1){ + String last = queues.removeLast(); + addLastNum(Integer.valueOf(last)); + } + } + + return new int[]{0, formula.length()}; + } + + private static boolean isSign(char c) { + if (c == '*' || c == '/' || c == '-' || c == '+') { + return true; + } + return false; + } + + private static boolean isNumber(char c) { + return c >= '0' && c <= '9'; + } + + private static int calculateExpress(String s1, String s2, String sign) { + int num1 = Integer.parseInt(s1); + int num2 = Integer.parseInt(s2); + int result = 0; + switch (sign) { + case "*": + result = num1 * num2; + break; + case "/": + result = num1 / num2; + break; + case "+": + result = num1 + num2; + break; + case "-": + result = num1 - num2; + break; + } + return result; + } + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringFullArrangement.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/StringFullArrangement.kt new file mode 100644 index 0000000..fc39e51 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringFullArrangement.kt @@ -0,0 +1,64 @@ +package com.wangpos.datastructure.algorithm + +import java.util.Collections.swap + +/** + * 分治法,顾名思义分而治之,将无法着手的大问题分解成一系列相同的小问题,然后逐一解决 + * + * 分治算法基本上可以归纳为三个步骤 + * + * 1. 分解:将问题分解为若干规模小的相同结构问题 + * + * 2. 解决:如果上一步问题可以解决就解决,否则,对每个子问题使用和上一步相同的方法再次分解,然后求解分解后的子问题,这个过程可能是一个递归的过程 + * + * 3. 合并:将上一步解决的各个子问题的解通过某种规则合并起来,得到原问题的解 + * + * 分治法难点,如果将子问题分解,并将子问题的解合并,针对不同的问题,有不同的分解与合并的方式 + * + * 递归作为一种算法的实现方式,与分治是一✅天然的好朋友。当然分治也可以用非递归方式实现出来,就是比较难 + * + * + */ +fun main(args: Array) { + + + var target = "abc" + var targetArray = target.toCharArray() + + var resultList = arrayListOf() + + stringFullArrangement(resultList, targetArray, 0, target.length) + println("输出字符串:$target 的所有排列") + println("全部排列方式:$resultList") + +} + +/** + * 以 a b 为例 ,第一次固定 a 不用交换(集合中还是 a,b)子集合还剩b, b start等于end 添加 ab, + * + * 第二次循环 ab 交换 (ba) 子集合还剩 a start等于end 添加 ba + * + * ba 交换回来 ab + */ +fun stringFullArrangement(resultList: ArrayList, target: CharArray, start: Int, end: Int) { + + if (start == end) {//相等表示就一个字母不需要全排列了 + println("add"+target.toList().toString()+"start$start") + resultList.add(target.toList().toString()) + } + for (i in start until end) { + swap(target, i, start)//start表示固定的位置,每个元素都换到start位置,其他在去全排列, + stringFullArrangement(resultList,target,start+1,end) + swap(target, i, start)//进行下一个字符固定时要将上一个固定的位置还原到原来位置 + } + +} + +fun swap(target: CharArray, first: Int, start: Int) { + if (target[first] != target[start]) { + var temp = target[first] + target[first] = target[start] + target[start] = temp + } +} + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringGetMinCut.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringGetMinCut.java new file mode 100644 index 0000000..31546c2 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringGetMinCut.java @@ -0,0 +1,19 @@ +package com.wangpos.datastructure.algorithm; + +public class StringGetMinCut { + + public static void main(String[]args){ + + String originStr = "ACDCDCDAD"; + + + //A 0 + //AC 1 + // ACD 2 + // ACDC 1 + // ACDCD 2 + // ACDCDC 2 + + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringISRotation.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringISRotation.java new file mode 100644 index 0000000..59c448c --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringISRotation.java @@ -0,0 +1,38 @@ +package com.wangpos.datastructure.algorithm; + +public class StringISRotation { + + public static void main(String[] args) { + + String a = "abcd1"; + String b = "1abcd"; + + boolean result = isRotation(a, b); + + System.out.println("结果:" + result); + } + + private static boolean isRotation(String a, String b) { + if (a == null || b == null || a.length() != b.length()) { + return false; + } + String b2 = b + b; + return StringKMP.getIndexOf(b2,a)!=-1; + } + + //KMP算法求是否包含子串 ,时间复杂度O(n+p) + private static int getIndexOf(String s, String m) { + + if (s == null || m == null || m.length() < 1 || s.length() < m.length()) { + return -1; + } + + char[] ss = s.toCharArray(); + char[] ms = m.toCharArray(); + int si = 0; + int mi = 0; +// int []next = getNextArray(ms); + return 0; + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringKMP.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringKMP.java new file mode 100644 index 0000000..a371502 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringKMP.java @@ -0,0 +1,74 @@ +package com.wangpos.datastructure.algorithm; + +public class StringKMP { + + public static void main(String[] args) { + String str1 = "ababacdcdefcdcag"; + String str2 = "cdca"; + + System.out.println(getIndexOf(str1, str2)); + } + + static int getIndexOf(String source, String target) { + return kmp(source.toCharArray(), target.toCharArray()); + } + + static int kmp(char[] s, char[] p) { + int nextLength = Math.min(s.length, p.length); + int i = 0; + int j = 0; + int[] next = new int[nextLength]; + getNextVal(p, next); + int sLen = s.length; + int pLen = p.length; + while (i < sLen && j < pLen) { + //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++ + if (j == -1 || s[i] == p[j]) { + i++;//等于-1 和匹配成功都++,为什么-1还++,表示前面还没有匹配成功 + j++; + } else { + //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j] + //next[j]即为j所对应的next值 + j = next[j]; + } + } + if (j == pLen) + return i - j; //返回p在S中的位置 + else + return -1; + } + + + /** + * 获取K值数组 + * + * @param p + * @param next + * + */ + static void getNextVal(char[] p, int next[]) { + int pLen = p.length; + next[0] = -1; + int k = -1; + int j = 0; + while (j < pLen-1) { + //p[k]表示前缀,p[j]表示后缀 + if (k == -1 || p[j] == p[k]) { + j++; + k++; +// next[j] = k; + + if (p[j] != p[k]) + next[j] = k; //之前只有这一行 + else + //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]] + next[j] = next[k]; + + } else { + k = next[k]; + + } + } + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringMaxLengthNoRepeatSequence.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMaxLengthNoRepeatSequence.java new file mode 100644 index 0000000..ccb5e76 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMaxLengthNoRepeatSequence.java @@ -0,0 +1,45 @@ +package com.wangpos.datastructure.algorithm; + +public class StringMaxLengthNoRepeatSequence { + + public static void main(String[] args) { + String str = "abcdaef"; + String str2 = "abcdaefd"; + System.out.println(getMaxLengthNoRepeatSequence(str2)); + + } + + /** + * 分析 + * abcda + * 当我们编译如果不之前没有出现过就算在内,如果出现过就保存当前不重复长度, + * 然后将启示位置跳转到重复元素第一次出现的地方,比如重复元素默认位置是-1 + * 当array[4]位置又出现a ,判断曾经出现过, + * 我们保存元素可以使用一个256数组即可,数组里面对应位置保存的是对应位置 + * + * @param s + * @return + */ + public static int getMaxLengthNoRepeatSequence(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] chas = s.toCharArray(); + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + int len = 0;//最长长度 + int pre = -1;//保存前一个位置 + int cur = 0;//保存当前位置到前一个重复位置的长度 + for (int i = 0; i != chas.length; i++) { + //重复字符串的头部,如果map[chas[i]]有值,证明就是重复的 + pre = Math.max(pre, map[chas[i]]); + cur = i - pre; + len = Math.max(len, cur); + map[chas[i]] = i; + } + return len; + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringMerged.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMerged.java new file mode 100644 index 0000000..274a887 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMerged.java @@ -0,0 +1,92 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.HashSet; +import java.util.List; + +/** + * 两个集合映射组合算法 + * + */ +public class StringMerged { + + public static int N = 262144;//9组数据 + private static String[] resultArray = new String[262144]; + private static int itemSize = 4;//每次最大4个 + public static int index = 0; + private static HashSet sets = new HashSet<>(); + + public HashSet findAllKindSplice(String str) { + sets.clear();//每次都清理掉集合 + char[] chas = new char[itemSize]; + + char[] strArray = str.toCharArray(); + for (int i = 0; i < chas.length; i++) { + chas[i] = ' '; + if (i < str.length()) { + chas[i] = strArray[i]; + } + } + + int length = itemSize; + for (int i = 0; i < resultArray.length; i++) { + char chasValue = ' '; + if (index == 0) { + if (i < N / length) { + chasValue = chas[0]; + } else if (i < N / length * 2) { + chasValue = chas[1]; + } else if (i < N / length * 3) { + chasValue = chas[2]; + } else if (i < N) { + if (chas[3] != ' ') { + chasValue = chas[3]; + } else { +// chasValue = '*'; + } + } + } else { + if (i % getCount(N, index - 1) < getCount(N, index)) {//0~3 + chasValue = chas[0]; + } else if (i % getCount(N, index - 1) < getCount(N, index) * 2) { + chasValue = chas[1]; + } else if (i % getCount(N, index - 1) < getCount(N, index) * 3) { + chasValue = chas[2]; + } else if (i % getCount(N, index - 1) < getCount(N, index) * 4) { + if (chas[3] != ' ') { + chasValue = chas[3]; + } + } + } + + if (resultArray[i] != null && chasValue != ' ') { + resultArray[i] = resultArray[i] + chasValue; + } else { + if (chasValue != ' ') { + resultArray[i] = "" + chasValue; + } else { + resultArray[i] = ""; + } + } + //去掉多于元素,因为本次拼接长度一定比上次长 + if (resultArray[i].length() > index) { + sets.add(resultArray[i]); + } + } + + index++; +// return resultArray; + return sets; + } + + private static int getCount(int n, int index) { + if (index == 0) { + return n / itemSize; + } else { + return getCount(n / itemSize, --index); + } + } + + + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringMerged2.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMerged2.java new file mode 100644 index 0000000..de97105 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMerged2.java @@ -0,0 +1,94 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.ArrayList; +import java.util.List; + +/** + * 两个集合映射组合算法 + */ +public class StringMerged2 { + public static Node root = new Node(null, "", null); + + public static List currentNodeList = new ArrayList<>(); + + public static void main(String[] args) { + String str1 = "ABC"; + String str2 = "DEF"; + String str3 = "GHI"; + String str4 = "JKL"; + String str5 = "MNO"; + String str6 = "PQRS"; + String str7 = "TUVW"; + String str8 = "XYZ"; + findMerged(str1); + findMerged(str2); + findMerged(str3); + findMerged(str4); + findMerged(str5); + findMerged(str6); + findMerged(str7); + + System.out.println("start=" + System.currentTimeMillis()); + List results = findMerged(str8); + System.out.println("end=" + System.currentTimeMillis()); +// for (Leaf leaf : currentLeafList) { +// System.out.print(leaf.value + ","); +// } + + System.out.println("总结果数量:" + results.size()); +// for (String result : results) { +// System.out.print(result + ","); +// } + +// List results = findMerged(str1); + } + + public static List findMerged(String str) { + char chas[] = str.toCharArray(); + + if (currentNodeList.size() == 0) { + List list = new ArrayList<>(); + for (char cha : chas) { + list.add(new Node(root, String.valueOf(cha), null)); + } + currentNodeList = list; + } else { + List nodeList = new ArrayList<>();//底部叶子节点 + for (Node node : currentNodeList) { + List childList = new ArrayList<>();//创建每一个孩子集合 + for (char cha : chas) { + Node child = new Node(node, String.valueOf(cha), null); + childList.add(child); + nodeList.add(child); + } + node.childs = childList; + } + currentNodeList = nodeList; + } + + List results = new ArrayList<>(); + for (Node node : currentNodeList) { + String end = ""; + Node current = node; + while (current != null) { + end = current.value + end; + current = current.parent; + } + results.add(end); + } + return results; + } + + + static class Node { + public Node parent; + public String value; + public List childs; + + public Node(Node p, String value, List childs) { + this.parent = p; + this.childs = childs; + this.value = value; + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringMinIncludeStringLength.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMinIncludeStringLength.java new file mode 100644 index 0000000..496bd40 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMinIncludeStringLength.java @@ -0,0 +1,4 @@ +package com.wangpos.datastructure.algorithm; + +public class StringMinIncludeStringLength { +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringMinShortDistance.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMinShortDistance.java new file mode 100644 index 0000000..52838d7 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringMinShortDistance.java @@ -0,0 +1,42 @@ +package com.wangpos.datastructure.algorithm; + +public class StringMinShortDistance { + + public static void main(String args[]) { + + String[] strings = new String[]{"1", "3", "3", "3", "2", "3", "1"}; + System.out.println(minDistance(strings, "2", "1")); + + } + + public static int minDistance(String[] strs, String str1, String str2) { + if (str1 == null || str2 == null) { + return -1; + } + if (str1.equals(str2)) { + return 0; + } + int last1 = -1; + int last2 = -1; + int min = Integer.MAX_VALUE; + for (int i = 0; i != strs.length; i++) { + if (strs[i].equals(str1)) { + if (last2 != -1) { + min = Math.min(min, i - last2); + } + last1 = i; + } + if (strs[i].equals(str2)) { + if (last1 != -1) { + min = Math.min(min, i - last1); + } + last2 = i; + } + } + + return min == Integer.MAX_VALUE ? -1 : min; + } + + //如果实现时间复杂度O(1),那么我们只能采用Hash表,因为两个参数,所以Value 还得用一个hash表 + //最后我们把这个表先生成就好了 +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringNewString.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringNewString.java new file mode 100644 index 0000000..c9c1362 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringNewString.java @@ -0,0 +1,4 @@ +package com.wangpos.datastructure.algorithm; + +public class StringNewString { +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringPerfectShuffleCard.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringPerfectShuffleCard.java new file mode 100644 index 0000000..2c801ca --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringPerfectShuffleCard.java @@ -0,0 +1,55 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; + +public class StringPerfectShuffleCard { + public static int N; + public static char chas[]; + + public static void main(String args[]) { + String str = "12345678"; + chas = str.toCharArray(); + N = str.length() / 2; + System.out.println(shuffleCard(str)); + } + + public static String shuffleCard(String str) { + + for (int i = 1; i < N; ) { + int startIndex = i; + int curIndex = -1; + char currentValue = ' '; + while (curIndex != startIndex) { + if (curIndex == -1) { + curIndex = startIndex; + int nextIndex = getNextPosition(curIndex); + currentValue = chas[nextIndex]; + chas[nextIndex] = chas[curIndex]; + curIndex = nextIndex; + } else { + int nextIndex = getNextPosition(curIndex); + char nextValue = chas[nextIndex]; + chas[nextIndex] = currentValue; + currentValue = nextValue; + curIndex = nextIndex; + } + } + + i = i + 2; + } + + return Arrays.toString(chas); + } + + private static char getValue(int curIndex) { + return chas[curIndex]; + } + + public static int getNextPosition(int postion) { + if (postion < N) { + return 2 * postion; + } else { + return 2 * (postion - N) + 1; + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringReplace.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringReplace.java new file mode 100644 index 0000000..d39cc15 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringReplace.java @@ -0,0 +1,53 @@ +package com.wangpos.datastructure.algorithm; + +public class StringReplace { + + public static void main(String[] args) { + + + } + + public static void replace(char[] chas) { + if (chas == null || chas.length == 0) { + return; + } + int num = 0; + int len = 0; + for (len = 0; len < chas.length && chas[len] != 0; len++) { + if (chas[len] == ' ') { + num++; + } + } + + int j = len + num * 2 - 1; + for (int i = len - 1; i > -1; i--) { + if (chas[i] != ' ') { + chas[j--] = chas[i]; + } else { + chas[j--] = '0'; + chas[j--] = '2'; + chas[j--] = '%'; + } + } + } + + /** + * 有字符串的包含数组和* ,现在把星移动到所有数字前面 + * @param chas + */ + public void modify(char[]chas){ + + if(chas ==null ||chas.length==0){ + return; + } + int j = chas.length-1; + for(int i= chas.length-1;i>-1;i--){ + if(chas[i]!='*'){ + chas[j--] = chas[i]; + } + } + for(;j>-1;){ + chas[j--] = '*'; + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringReverse.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringReverse.java new file mode 100644 index 0000000..31bb719 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringReverse.java @@ -0,0 +1,89 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.Arrays; + +public class StringReverse { + + + public static void main(String[] args) { + + String str = "I love you"; + + String result = getReverse2(str); + System.out.println(result); + + } + + //通过char 数组事项 + private static String getReverse(String str) { + char[] chas = str.toCharArray(); + int start = 0; + int end = chas.length - 1; + /** + * 实现思路 先全部反转,然后再局部反转 + */ + reverse(chas, start, end); + + int l = -1; + int r = -1; + for (int i = 0; i < chas.length; i++) { + if (chas[i] != ' ') { + //找到l + if (i == 0 || chas[i - 1] == ' ') { + l = i; + } + //找到每一个r + if (i == chas.length - 1 || chas[i + 1] == ' ') { + r = i; + } + } + if (l != -1 && r != -1) { + reverse(chas, l, r); + l = -1; + r = -1; + } + } + + return Arrays.toString(chas); + } + + //通过String 数组也可以实现,但是会产生临时对象 + private static String getReverse2(String str) { + String strArray[] = str.split(" "); + + int start = 0; + int end = strArray.length - 1; + String tmp = ""; + while (start < end) { + tmp = strArray[start]; + strArray[start] = strArray[end]; + strArray[end] = tmp; + start++; + end--; + } + + String result = "";//StringBuilder + for (int i = 0; i < strArray.length; i++) { + if (i == strArray.length - 1) { + result = result+strArray[i]; + } else { + result = result + strArray[i] + " "; + } + + } + return result; + } + + + public static void reverse(char[] chas, int start, int end) { + char tmp = 0; + while (start < end) { + tmp = chas[start]; + chas[start] = chas[end]; + chas[end] = tmp; + start++; + end--; + } + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringRotateChange.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringRotateChange.java new file mode 100644 index 0000000..843a897 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringRotateChange.java @@ -0,0 +1,52 @@ +package com.wangpos.datastructure.algorithm; + + +/** + * 旋转字符串 + *

+ *

+ */ +public class StringRotateChange { + + public static void main(String[] args) { + + String s1 = "abcd"; + String s2 = "dbac";//true + + System.out.println("是否是旋转串=" + isRotateString(s1, s2)); + + String s3 = "abcd"; + String s4 = "cadb";//false + + System.out.println("是否是旋转串=" + isRotateString(s3, s4)); + } + + public static boolean isRotateString(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0 || s1.length() != s2.length()) { + return false; + } + if (s1 == s2) { + return true; + } + + char[] chas1 = s1.toCharArray(); + char[] chas2 = s2.toCharArray(); + + /** + * 从上向下比较 + */ + int start = 0; + int end = chas2.length-1; + for (int i = chas1.length-1; i >0; i--) { + if (chas1[i] != chas2[end]) { + if (chas1[i] != chas2[start]) { + return false; + } else { + start++; + } + } + } + + return true; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringSingleChar.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringSingleChar.java new file mode 100644 index 0000000..142dbe9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringSingleChar.java @@ -0,0 +1,108 @@ +package com.wangpos.datastructure.algorithm; + +public class StringSingleChar { + + public static void main(String[] args) { + String testStr1 = "aaabbb"; + String testStr2 = "abcd"; + + System.out.println(isSingleCharString(testStr1)); + System.out.println(isSingleCharString(testStr2)); + + System.out.println(isUnique2(testStr1.toCharArray())); + System.out.println(isUnique2(testStr2.toCharArray())); + } + + // O (n) + public static boolean isSingleCharString(String str) { + char[] chars = str.toCharArray(); + int[] counts = new int[256]; + for (char aChar : chars) { + if (counts[aChar] > 0) { + return false; + } + counts[aChar]++; + } + return true; + } + + // 要求空间复杂度O 1 + /** + * 所有O(n)时间复杂度的排序算法(桶排序,基数排序,计数排序)都需要额外空间排序,所以空间复杂度不是O(1) + * 那么看O(nlogn)时间复杂度算法 (归并排序,快速排序,希尔排序,堆排序) + * 归并排序中有连个数组合并成一个数组过程,这个过程需要辅助数组来完成 + * 快速排序额外空间复杂度最低 logN + * 希尔排序同样也排除,因为他的时间复杂度不固定,最低N*N,取决于步长 + * 堆排序 可以时间复杂度能稳定O(NlogN) 并且空间复杂度是O(1),但是要使用非递归实现 + * 否则会浪费函数栈空间 + * + * @param chas + * @return + */ + //堆排序 O(nlogn) 空间复杂度O(1) + public static boolean isUnique2(char[] chas) { + + if (chas == null) { + return true; + } + heapSort(chas); + for (int i = 1; i < chas.length; i++) { + if (chas[i] == chas[i - 1]) { + return false; + } + } + return true; + } + + public static void heapSort(char[] chas) { + for (int i = 0; i < chas.length; i++) { + heapInsert(chas, i); + } + for (int i = chas.length - 1; i > 0; i--) { + swap(chas, 0, i); + heapify(chas, 0, i); + } + } + + public static void heapify(char[] chas, int i, int size) { + int left = i * 2 + 1; + int right = i * 2 + 2; + int largest = i; + while ((left < size)) { + if (chas[left] > chas[i]) { + largest = left; + } + if (right < size && chas[right] > chas[largest]) { + largest = right; + } + if (largest != i) { + swap(chas, largest, i); + } else { + break; + } + i = largest; + left = i * 2 + 1; + right = i * 2 + 2; + } + } + + public static void heapInsert(char[] chas, int i) { + int parent = 0; + while (i != 0) { + + parent = (i - 1) / 2; + if (chas[parent] < chas[i]) { + swap(chas, parent, i); + i = parent; + } else { + break; + } + } + } + + public static void swap(char[] chas, int index1, int index2) { + char tmp = chas[index1]; + chas[index1] = chas[index2]; + chas[index2] = tmp; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringToDiKaEr.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToDiKaEr.java new file mode 100644 index 0000000..8aa4262 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToDiKaEr.java @@ -0,0 +1,103 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.ArrayList; +import java.util.List; + +public class StringToDiKaEr { + + + public static void main(String[] args) { + List> list = new ArrayList>(); + List listSub1 = new ArrayList(); + List listSub2 = new ArrayList(); + List listSub3 = new ArrayList(); + List listSub4 = new ArrayList(); + List listSub5 = new ArrayList(); + List listSub6 = new ArrayList(); + List listSub7 = new ArrayList(); + List listSub8 = new ArrayList(); + listSub1.add("A"); + listSub1.add("B"); + listSub1.add("C"); + + listSub2.add("D"); + listSub2.add("E"); + listSub2.add("F"); + + listSub3.add("G"); + listSub3.add("H"); + listSub3.add("I"); + + listSub4.add("J"); + listSub4.add("K"); + listSub4.add("L"); + + listSub5.add("M"); + listSub5.add("N"); + listSub5.add("O"); + + listSub6.add("P"); + listSub6.add("Q"); + listSub6.add("R"); + + listSub7.add("S"); + listSub7.add("T"); + listSub7.add("U"); + + listSub8.add("V"); + listSub8.add("W"); + listSub8.add("X"); + + + list.add(listSub1); + list.add(listSub2); + list.add(listSub3); + list.add(listSub4); + list.add(listSub5); + list.add(listSub6); + list.add(listSub7); + list.add(listSub8); + + System.out.println("start:"+System.currentTimeMillis()); + List> result = new ArrayList>(); + descartes(list, result, 0, new ArrayList()); + System.out.println("end:"+System.currentTimeMillis()); + } + /** + * Created on 2014年4月27日 + *

+ * Discription:笛卡尔乘积算法 + * 把一个List{[1,2],[3,4],[a,b]}转化成List{[1,3,a],[1,3,b],[1,4 + * ,a],[1,4,b],[2,3,a],[2,3,b],[2,4,a],[2,4,b]}数组输出 + *

+ * + * @param layer + * 中间参数 + * @param curList + * 中间参数 + */ + private static void descartes(List> dimvalue, + List> result, int layer, List curList) { + if (layer < dimvalue.size() - 1) { + if (dimvalue.get(layer).size() == 0) { + descartes(dimvalue, result, layer + 1, curList); + } else { + for (int i = 0; i < dimvalue.get(layer).size(); i++) { + List list = new ArrayList(curList); + list.add(dimvalue.get(layer).get(i)); + descartes(dimvalue, result, layer + 1, list); + } + } + } else if (layer == dimvalue.size() - 1) { + if (dimvalue.get(layer).size() == 0) { + result.add(curList); + } else { + for (int i = 0; i < dimvalue.get(layer).size(); i++) { + List list = new ArrayList(curList); + list.add(dimvalue.get(layer).get(i)); + result.add(list); + } + } + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringToInt.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToInt.java new file mode 100644 index 0000000..baf9199 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToInt.java @@ -0,0 +1,82 @@ +package com.wangpos.datastructure.algorithm; + +public class StringToInt { + + public static void main(String[] args) { + + String str = "-123"; + String str2 = "a2342"; + String str3 = "02342"; + int result = convert(str); + + System.out.println(result); + } + + /** + * 检测是否是数字字符 + * + * @param chas + * @return + */ + public static boolean isValid(char[] chas) { + //判断第一位是0到9的之外的数,而不是负号 + if (chas[0] != '-' && (chas[0] < '0' || chas[0] > '9')) { + return false; + } + //如果第一位为负号,后面的不可以是0 也不能没有值 + if (chas[0] == '-' && (chas.length == 1 || chas[1] == '0')) { + return false; + } + //第一位是0 但是长度大于1 false + if (chas[0] == '0' && chas.length > 1) { + return false; + } + + //第二位以后是0 到9的数 + for (int i = 1; i < chas.length; i++) { + if (chas[i] < '0' || chas[i] > '9') { + return false; + } + } + return true; + } + + public static int convert(String str) { + if (str == null || str.equals("")) { + return 0;//不能转 + } + char[] chas = str.toCharArray(); + if (!isValid(chas)) { + return 0;//不符合要求 + } + + //判断正负数 + boolean posi = chas[0] != '-'; + int minq = Integer.MIN_VALUE / 10; + int minr = Integer.MAX_VALUE % 10; + int res = 0; + int cur = 0; + //-2147483648 2147483647 + // + //左程云. 程序员代码面试指南IT名企算法与数据结构题目最优解(第2版) (Chinese Edition) (Kindle位置3654). Kindle 版本. + // + //左程云. 程序员代码面试指南IT名企算法与数据结构题目最优解(第2版) (Chinese Edition) (Kindle位置3654). Kindle 版本. + //以负数进行计算 比如 + // 123 第一次 -1 res = 0*10 -1 =-1 小于12 + // 第二次 res = -1*10 -2 = -12 小于12 如果大于直接溢出,不管下一位是什么 + // 第三次 res = -12*10 -3 = -123 如果等于 就比较最后一个值 + for (int i = posi ? 0 : 1; i < chas.length; i++) { + cur = '0' - chas[i];//当前字符所代表的负数形式 + if ((res < minq) || (res == minq && cur < minr)) { + return 0; + } + res = res * 10 + cur; + + } + //判断正数最大值 + if (posi && res == Integer.MIN_VALUE) { + return 0;//不能转 + } + return posi ? -res : res; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringToStatisticsString.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToStatisticsString.java new file mode 100644 index 0000000..39638a9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToStatisticsString.java @@ -0,0 +1,39 @@ +package com.wangpos.datastructure.algorithm; + +public class StringToStatisticsString { + + + public static void main(String[] args) { + + String target = "aaabbbbbccc3333cccdee"; + + String result = getCountString(target); + + System.out.println(result); + + } + + public static String getCountString(String str) { + + char[] chs = str.toCharArray(); + String res = String.valueOf(chs[0]); + + int num = 1; + + for (int i = 1; i < chs.length; i++) { + if (chs[i] != chs[i - 1]) { + res = contact(res,String.valueOf(num),String.valueOf(chs[i])); + num = 1; + }else{ + num++; + } + } + return contact(res,String.valueOf(num),""); + } + + //统计字符串用_分割开,可以区分里面含数字的情况 + public static String contact(String s1,String s2,String s3){ + return s1+"_"+s2+(s3.equals("")?s3:"_"+s3); + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/StringToStringMinDistance.java b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToStringMinDistance.java new file mode 100644 index 0000000..415e87f --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/StringToStringMinDistance.java @@ -0,0 +1,118 @@ +package com.wangpos.datastructure.algorithm; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +public class StringToStringMinDistance { + + public static void main(String[] args) { + + String start = "adc"; + String to = "cab"; + List words = new ArrayList<>(); + words.add("cab"); + words.add("acc"); + words.add("cbc"); + words.add("ccc"); + words.add("cac"); + words.add("cbb"); + words.add("aab"); + words.add("abb"); + + List> list = findMinPaths(start, to, words); + + for (List strings : list) { + for (String str:strings){ + System.out.print(" "+str+" "); + } + System.out.println(); + } + } + + public static List> findMinPaths(String start, String to, List list) { + list.add(start); + HashMap> nexts = getNexts(list); + HashMap distances = getDistance(start, nexts); + LinkedList pathList = new LinkedList<>(); + List> res = new ArrayList<>(); + getShortestPaths(start, to, nexts, distances, pathList, res); + return res; + } + + private static void getShortestPaths(String cur, String to, HashMap> nexts, + HashMap distances, LinkedList solution, + List> res) { + solution.add(cur); + if (to.equals(cur)) { + res.add(new LinkedList(solution)); + } else { + for (String next : nexts.get(cur)) { + if (distances.get(next) == distances.get(cur) + 1) { + getShortestPaths(next, to, nexts, distances, solution, res); + } + } + } + solution.pollLast(); + } + + + public static HashMap getDistance(String start, HashMap> nexts) { + HashMap distances = new HashMap<>(); + distances.put(start, 0); + Queue queue = new LinkedList(); + queue.add(start); + HashSet set = new HashSet<>(); + set.add(start); + while (!queue.isEmpty()) { + String cur = queue.poll(); + for (String str : nexts.get(cur)) { + if (!set.contains(str)) { + distances.put(str, distances.get(cur) + 1); + queue.add(str); + set.add(str); + } + } + } + + return distances; + } + + public static HashMap> getNexts(List words) { + Set dict = new HashSet<>(words); + + HashMap> nexts = new HashMap<>(); + for (int i = 0; i < words.size(); i++) { + nexts.put(words.get(i), new ArrayList()); + } + for (int i = 0; i < words.size(); i++) { + nexts.put(words.get(i), getNext(words.get(i), dict)); + } + return nexts; + } + + private static ArrayList getNext(String word, Set dict) { + ArrayList res = new ArrayList<>(); + char[] chs = word.toCharArray(); + for (char cur = 'a'; cur <= 'z'; cur++) { + for (int i = 0; i < chs.length; i++) { + if (chs[i] != cur) { + char tmp = chs[i]; + + chs[i] = cur; //替换 + if (dict.contains(String.valueOf(chs))) { + res.add(String.valueOf(chs)); + } + chs[i] = tmp; //替换回来 + } + } + } + return res; + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/TestStringMerged.java b/app/src/main/java/com/wangpos/datastructure/algorithm/TestStringMerged.java new file mode 100644 index 0000000..cadf3b7 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/TestStringMerged.java @@ -0,0 +1,54 @@ +package com.wangpos.datastructure.algorithm; + +import java.util.HashMap; +import java.util.HashSet; + +public class TestStringMerged { + + public static void main(String[] args) { + + String str1 = "ABCD"; + + String str2 = "EFGH"; + + String str3 = "IJK"; + + String str4 = "LMN"; + + String str5 = "OPQ"; + + String str6 = "RST"; + + String str7 = "UVW"; + + String str8 = "XYZ"; + + StringMerged sm = new StringMerged(); + + sm.findAllKindSplice(str1); + sm.findAllKindSplice(str2); + sm.findAllKindSplice(str3); + sm.findAllKindSplice(str4); + sm.findAllKindSplice(str5); + sm.findAllKindSplice(str6); + sm.findAllKindSplice(str7); + System.out.println("startTime " + System.currentTimeMillis()); + HashSet resultSet = sm.findAllKindSplice(str8); + System.out.println("endTime " + System.currentTimeMillis()); + + +// for (String s : resultArray) { +// System.out.print(s + " "); +// } + + System.out.println(); +// System.out.println("______________________"); +//// + System.out.println("种类" + resultSet.size()); +// for (String s : resultSet) { +// System.out.print(s + " "); +// } + + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/TwoSeparate.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/TwoSeparate.kt new file mode 100644 index 0000000..b711005 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/TwoSeparate.kt @@ -0,0 +1,36 @@ +package com.wangpos.datastructure.algorithm + + +fun main(args: Array) { + + var result = twoSepearate(-0.8,8.0) + + println("方程的近似解=$result") +} + +/** + * 二分法的局限性就是不能计算复根和重根,需要借助其手段确定零点所在区间。设方程为 f(x) = 2x^{2} + 3.2x - 1.8f(x)=2x +2 ++3.2x−1.8,求根精度是 PRECISION = 0.000000001,在 [-0.8,8.0] 区间上求解 xx = 0.440967364 + */ +fun f(x: Double): Double { + return 2.0 * x * x + 3.2 * x - 1.8 +} + + +fun twoSepearate(aa: Double, bb: Double): Double { + var PRECISION = 0.00000000001 + var a = aa + var b = bb + var mid = (a + b) / 2.0 + + while ((b - a) > PRECISION) { + if (f(a) * f(mid) < 0.0) { + b = mid + } else { + a = mid + } + mid = (a + b) / 2.0 + } + return mid +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion100buychicken.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion100buychicken.kt new file mode 100644 index 0000000..c0d1f2d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion100buychicken.kt @@ -0,0 +1,62 @@ +package com.wangpos.datastructure.algorithm + + +/** + *穷举法又称穷举搜索法,是一种在问题域的解空间中对所有可能的解穷举搜索,并根据条件选择最优解的方法的总称。 + * 数学上也把穷举法称为枚举法,就是在一个由有限个元素构成的集合中,把所有元素一一枚举研究的方法。 + * + * 穷举法的敌人就是规模大,解空间大 + * + *一般来说,只要一个问题有其他更好的方法解决,通常不会选择穷举法,穷举法也常被作为“不是办法的办法”或“最后的办法”来使用,但是绝对不能因为这样而轻视穷举法,穷举法在算法设计模式中占有非常重要的地位,它还是很多问题的唯一解决方法。 + * + * + * + */ + +fun main(args: Array) { + + var count = buychicken(100) + + println("百钱买鸡共有 $count 种方法") + +} + + +/** + * + * * + *一百个钱买一百只鸡,是个典型的穷举法应用。问题描述:每只大公鸡值 5 个钱, + * 每只母鸡值 3 个钱, + * 每 3 只小鸡值 1 个钱, + * 现在有 100 个钱,想买 100 只鸡,问如何买?有多少种方法? + * + * 公鸡枚举的空间 0 20 + * + * 母鸡枚举的空间 0 33 + */ + +fun buychicken(money: Int): Int { + + var maxGJ = 20 + + var maxMJ = 33 + + var count = 0 + + var startTime = System.currentTimeMillis() + for (i in 0 until 21) { + // 剪枝操作,提高效率 + var maxCount = (100 - i*5)/3 + for (j in 0 until maxCount) { + var smallChicken = 100 - i - j + if (smallChicken % 3 == 0 && (smallChicken / 3 + j * 3 + i * 5) == 100){ + println("公鸡=$i ,母鸡=$j ,小鸡=$smallChicken") + count++ + } + } + } + println("算法花费时间"+(System.currentTimeMillis()-startTime)) + + + return count +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion24.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion24.kt new file mode 100644 index 0000000..09ef959 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustion24.kt @@ -0,0 +1,99 @@ +package com.wangpos.datastructure.algorithm + +import kotlin.reflect.KFunction3 + +/** + * + * 穷举加分治法,子问题就是求两个数的四则运算 + */ +fun main(args: Array) { + + var arrays = arrayOf(::add, ::sub, ::multiply,::divide) + var nums = arrayListOf( + Number(3.toDouble(), "3"), + Number(2.toDouble(), "2"), + Number(6.toDouble(), "6"), + Number(7.toDouble(), "7") + ) + Calc24(nums, arrays) + +} + +class Number(var num: Double, var num_str: String) + +fun add(number1: Number, number2: Number, next: (Number) -> Unit): Boolean { + var result = number1.num + number2.num + var newNumber = Number(result, "("+number1.num_str + "+" + number2.num_str+")") + next(newNumber) + return true +} + +fun sub(number1: Number, number2: Number, next: (Number) -> Unit): Boolean { + var result = number1.num - number2.num + var newNumber = Number(result, "("+number1.num_str + "-" + number2.num_str+")") + next(newNumber) + return true +} + +fun multiply(number1: Number, number2: Number, next: (Number) -> Unit): Boolean { + var result = number1.num * number2.num + var newNumber = Number(result, "("+number1.num_str + "*" + number2.num_str+")") + next(newNumber) + return true + +} + +fun divide(number1: Number, number2: Number, next: (Number) -> Unit): Boolean { + if (number2.num == 0.toDouble()) return false + var result = number1.num / number2.num +// println("////$result") + var newNumber = Number(result, "("+number1.num_str + "/" + number2.num_str+")") + next(newNumber) + return true +} + +fun Calc24( + nums: ArrayList, + operations: Array Unit, Boolean>> +) { + if (nums.size == 1) { + if (nums[0].num.toInt() == 24) { + println(nums[0].num_str + "=" + nums[0].num) + } + return + } +// println("---------") +// nums.forEach { +// println(it.num_str + "=" + it.num) +// } +// println("---------") + + for (i in nums.indices) { + for (j in nums.indices) { + if (i == j) continue + + operations.forEach { + var newNumber: Number? = null + if (it(nums[i], nums[j]) { + newNumber = it + }) { + var list = arrayListOf() + + // 新数据添加集合 + newNumber?.let { it1 -> list.add(it1) } + + for (k in nums.indices) { + if (k == i || k == j) continue + // 将剩余数添加到集合 + list.add(nums[k]) + } + + Calc24(list, operations) + } + } + } + } +} + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionAlbertEinstein.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionAlbertEinstein.kt new file mode 100644 index 0000000..729f66d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionAlbertEinstein.kt @@ -0,0 +1,508 @@ +package com.wangpos.datastructure.algorithm + + +/** + * (1)英国人住在红色的房子里 +(2)瑞典人养狗作为宠物 +(3)丹麦人喝茶 +(4)绿房子紧挨着白房子,在白房子的左边 +(5)绿房子的主人喝咖啡 +(6)抽 Pall Mall 牌香烟的人养鸟 +(7)黄色房子里的人抽 Dunhill 牌香烟 + +(8)住在中间那个房子里的人喝牛奶 +(9)挪威人住在第一个房子里面 + +(10)抽 Blends 牌香烟的人和养猫的人相邻 +(11)养马的人和抽 Dunhill 牌香烟的人相邻 +(12)抽 BlueMaster 牌香烟的人喝啤酒 +(13)德国人抽 Prince 牌香烟 +(14)挪威人和住在蓝房子的人相邻 +(15)抽 Blends 牌香烟的人和喝矿泉水的人相邻 + */ +val yellow = 1 +val blue = 2 +val red = 3 +val green = 4 +val white = 5 + +val water = 1 +val tea = 2 +val milk = 3 +val coffee = 4 +val beer = 5 + +val nuowei = 1 +val danmai = 2 +val yingguo = 3 +val deguo = 4 +val ruidian = 5 + +val cat = 1 +val horse = 2 +val bird = 3 +val fish = 4 +val dog = 5 + +val Dunhill = 1 +val Blends = 2 +val PallMall = 3 +val Prince = 4 +val BlueMaster = 5 + + +var roomColors = arrayOf(1, 2, 3, 4, 5) +var drinks = arrayOf(1, 2, 3, 4, 5) +var countrys = arrayOf(1, 2, 3, 4, 5) +var pets = arrayOf(1, 2, 3, 4, 5) +var tobaccos = arrayOf(1, 2, 3, 4, 5) + +var cnt: Long = 0 + +/** + * 先填表,最后在赛选,也可以用递归实现 + */ +fun main() { + + var groups = arrayOf(Person_E(), Person_E(), Person_E(), Person_E(), Person_E()) + + assignedColor(roomColors, groups) +} + +/** + * 分配颜色 + */ +private fun assignedColor( + roomColors: Array, + groups: Array +) { + for (i in roomColors.indices) { + for (j in roomColors.indices) { + if (i == j) continue + for (k in roomColors.indices) { + if (k == i || k == j) continue + for (l in roomColors.indices) { + if (l == i || l == j || l == k) continue + for (m in roomColors.indices) { + if (m == i || m == j || m == k || m == l) continue + + groups[0].room_color = roomColors[i] + groups[1].room_color = roomColors[j] + groups[2].room_color = roomColors[k] + groups[3].room_color = roomColors[l] + groups[4].room_color = roomColors[m] + // 剪枝操作 + // 再分配国家 + for (groupIdx in groups.indices) { + if (groupIdx + 1 < groups.size) { + var ad = "sd" + ad.isEmpty() + if (groups[groupIdx].room_color === green + && groups[groupIdx + 1].room_color == white + ) { + assignedCountry(groups) + } + } + } + } + } + } + } + } +} + +/** + * 分配国家 + */ +fun assignedCountry(groups: Array) { + /*应用规则(9):挪威人住在第一个房子里面;*/ + groups[0].country = nuowei; + + for (i in countrys.indices) { + for (j in countrys.indices) { + if (i == j) continue + for (k in countrys.indices) { + if (k == i || k == j) continue + for (l in countrys.indices) { + if (l == k || l == j || l == i) continue + for (m in countrys.indices) { + if (m == i || m == j || m == k || m == l) continue + groups[1].country = countrys[j] + groups[2].country = countrys[k] + groups[3].country = countrys[l] + groups[4].country = countrys[m] + + assigndDrink(groups) + } + } + } + } + } +} + +/** + * 分配喝的 + */ +fun assigndDrink(groups: Array) { + /*应用规则(8):住在中间那个房子里的人喝牛奶;*/ + groups[2].drink = milk + for (i in drinks.indices) { + for (j in drinks.indices) { + if (i == j) continue + for (k in drinks.indices) { + if (k == i || k == j) continue + for (l in drinks.indices) { + if (l == k || l == j || l == i) continue + for (m in drinks.indices) { + if (m == i || m == j || m == k || m == l) continue + groups[0].drink = drinks[i] + groups[1].drink = drinks[j] + groups[3].drink = drinks[l] + groups[4].drink = drinks[m] + + assigndPet(groups) + } + } + } + } + } +} + +/** + * 分配宠物 + */ +fun assigndPet(groups: Array) { + + for (i in pets.indices) { + for (j in pets.indices) { + if (i == j) continue + for (k in pets.indices) { + if (k == i || k == j) continue + for (l in pets.indices) { + if (l == k || l == j || l == i) continue + for (m in pets.indices) { + if (m == i || m == j || m == k || m == l) continue + groups[0].pet = pets[i] + groups[1].pet = pets[j] + groups[2].pet = pets[k] + groups[3].pet = pets[l] + groups[4].pet = pets[m] + + assignedTabacco(groups) + } + } + } + } + } +} + +fun assignedTabacco(groups: Array) { + for (i in tobaccos.indices) { + for (j in tobaccos.indices) { + if (i == j) continue + for (k in tobaccos.indices) { + if (k == i || k == j) continue + for (l in tobaccos.indices) { + if (l == k || l == j || l == i) continue + for (m in tobaccos.indices) { + if (m == i || m == j || m == k || m == l) continue + groups[0].tobacco = tobaccos[i] + groups[1].tobacco = tobaccos[j] + groups[2].tobacco = tobaccos[k] + groups[3].tobacco = tobaccos[l] + groups[4].tobacco = tobaccos[m] + DoGroupsfinalCheck(groups) + } + } + } + } + } +} + +/** + * + */ + + +fun DoGroupsfinalCheck(groups: Array) { +// cnt++ +// println(cnt) + + if (checkResult(groups)) { + PrintAllGroupsResult(groups) + println() + } +} + +/** + * + * + * * (1)英国人住在红色的房子里 +(2)瑞典人养狗作为宠物 +(3)丹麦人喝茶 +(4)绿房子紧挨着白房子,在白房子的左边 +(5)绿房子的主人喝咖啡 +(6)抽 Pall Mall 牌香烟的人养鸟 +(7)黄色房子里的人抽 Dunhill 牌香烟 + + +(10)抽 Blends 牌香烟的人和养猫的人相邻 +(11)养马的人和抽 Dunhill 牌香烟的人相邻 +(12)抽 BlueMaster 牌香烟的人喝啤酒 +(13)德国人抽 Prince 牌香烟 +(14)挪威人和住在蓝房子的人相邻 +(15)抽 Blends 牌香烟的人和喝矿泉水的人相邻 + + */ +fun checkResult(groups: Array): Boolean { + + for (i in groups.indices) { + var it = groups[i] + //(1)英国人住在红色的房子里 + if (it.room_color == red) { + if (it.country != yingguo) { + return false + } + } + + //(2)瑞典人养狗作为宠物 + if (it.country == ruidian) { + if (it.pet != dog) { + return false + } + } + + //(3)丹麦人喝茶 + if (it.country == danmai) { + if (it.drink != tea) { + return false + } + } + + //(5)绿房子的主人喝咖啡鸟 + if (it.room_color == green) { + if (it.drink != coffee) { + return false + } + } + + //(6)抽 Pall Mall 牌香烟的人养鸟 + if (it.pet == bird) { + if (it.tobacco != PallMall) { + return false + } + } + + //(7)黄色房子里的人抽 Dunhill 牌香烟 + if (it.room_color == yellow) { + if (it.tobacco != Dunhill) { + return false + } + } + + //(12)抽 BlueMaster 牌香烟的人喝啤酒 + if (it.tobacco == BlueMaster) { + if (it.drink != beer) { + return false + } + } + + //13 德国人抽 Prince 牌香烟 + if (it.country == deguo) { + if (it.tobacco != Prince) { + return false + } + } + + + //(14)挪威人和住在蓝房子的人相邻 + if (it.country == nuowei) { + + var first = if ((i - 1) < 0) { + false + } else { + groups[i - 1].room_color == blue + } + + var second = if ((i + 1) >= groups.size) { + false + } else { + groups[i + 1].room_color == blue + } + if (!first && !second) { + return false + } + } + + // 15)抽 Blends 牌香烟的人和喝矿泉水的人相邻 + if (it.tobacco == Blends) { + + var first = if ((i - 1) < 0) { + false + } else { + groups[i - 1].drink == water + } + + var second = if ((i + 1) >= groups.size) { + false + } else { + groups[i + 1].drink == water + } + if (!first && !second) { + return false + } + } + + // (10)抽 Blends 牌香烟的人和养猫的人相邻 + if (it.tobacco == Blends) { + var first = if ((i - 1) < 0) { + false + } else { + groups[i - 1].pet == cat + } + + var second = if ((i + 1) >= groups.size) { + false + } else { + groups[i + 1].pet == cat + } + if (!first && !second) { + return false + } + } + + //(11)养马的人和抽 Dunhill 牌香烟的人相邻 + if (it.tobacco == Dunhill) { + var first = if ((i - 1) < 0) { + false + } else { + groups[i - 1].pet == horse + } + + var second = if ((i + 1) >= groups.size) { + false + } else { + groups[i + 1].pet == horse + } + if (!first && !second) { + return false + } + } + + //(4)绿房子紧挨着白房子,在白房子的左边 + if ((i + 1) < groups.size) { + if (groups[i].room_color == green && groups[i + 1].room_color != white) { + return false + } + } + + } + + return true + +} + +/** + * + * +国家 房子 宠物 饮料 香烟 +挪威 黄色 猫 矿泉水 Dunhill +丹麦 蓝色 马 茶 Blends +英国 红色 鸟 牛奶 PallMall +德国 绿色 鱼 咖啡 Prince +瑞典 白色 狗 啤酒 BlueMaster + + +房间颜色:黄 国家:挪威 宠物:猫 饮料:水 抽烟:Dunhill +房间颜色:蓝 国家:丹麦 宠物:马 饮料:茶 抽烟:Blends +房间颜色:红 国家:英国 宠物:鸟 饮料:牛奶 抽烟:PallMall +房间颜色:绿 国家:德国 宠物:鱼 饮料:咖啡 抽烟:Prince +房间颜色:白 国家:瑞典 宠物:狗 饮料:啤酒 抽烟:BlueMaster + */ +fun PrintAllGroupsResult(groups: Array) { + + groups.forEach { + print("房间颜色:${getColorName(it.room_color)} ") + print("国家:${getCountryName(it.country)} ") + print("宠物:${getPetName(it.pet)} ") + print("饮料:${getDrinkName(it.drink)} ") + println("抽烟:${getTabaccoName(it.tobacco)} ") + } +} + +fun getColorName(room_color: Int): String { + return when (room_color) { + 1 -> "黄" + 2 -> "蓝" + 3 -> "红" + 4 -> "绿" + 5 -> "白" + else -> { + " " + } + } +} + +fun getCountryName(room_color: Int): String { + return when (room_color) { + 1 -> "挪威" + 2 -> "丹麦" + 3 -> "英国" + 4 -> "德国" + 5 -> "瑞典" + else -> { + " " + } + } +} + +fun getPetName(room_color: Int): String { + return when (room_color) { + 1 -> "猫" + 2 -> "马" + 3 -> "鸟" + 4 -> "鱼" + 5 -> "狗" + else -> { + " " + } + } +} + +fun getDrinkName(room_color: Int): String { + return when (room_color) { + 1 -> "水" + 2 -> "茶" + 3 -> "牛奶" + 4 -> "咖啡" + 5 -> "啤酒" + else -> { + " " + } + } +} + + +fun getTabaccoName(room_color: Int): String { + return when (room_color) { + 1 -> "Dunhill" + 2 -> "Blends" + 3 -> "PallMall" + 4 -> "Prince" + 5 -> "BlueMaster" + else -> { + " " + } + } +} + +/** + * 房子颜色 国籍 饮料 宠物 烟 + */ +class Person_E( + var room_color: Int = -1, + var country: Int = -1, + var drink: Int = -1, + var pet: Int = -1, + var tobacco: Int = -1 +) diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionUpstairs.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionUpstairs.kt new file mode 100644 index 0000000..96f9ea5 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionUpstairs.kt @@ -0,0 +1,20 @@ +package com.wangpos.datastructure.algorithm + +/** + * 小明上楼梯有个习惯,就是一次要么走 1 级台阶,要么走 2 级台阶,有一个楼梯有 20 级台阶,问按照小明的习惯,他有多少种上楼梯的方法。 + * + * + * + * + * + * + * + * + */ +fun main() { + + + +} + + diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionbucket.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionbucket.kt new file mode 100644 index 0000000..a66d892 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionbucket.kt @@ -0,0 +1,180 @@ +package com.wangpos.datastructure.algorithm + + +/** + * 8 5 3 分 4升水,一共有多少种分发 + * + * 首先找到分析倒水有个动作,是谁到给了谁 + * + * 然后我们穷举所有倒水可能,放到一个列表中 + * + * + * 初始状态 8 0 0 + * 当最终水的状态为 4 4 0 为止, + * + * 数据模型为了方便最查找,每个状态包含一个parent + * + * 我们针对已经遍历过的状态进行剪枝操作,否者会死循环 + * + */ +fun main() { + + var actions = arrayOf( + arrayOf(0, 1), arrayOf(0, 2), arrayOf(1, 0), arrayOf(1, 2), arrayOf(2, 0), arrayOf(2, 1) + ) + + var b8 = Bucket(8, 8) + var b5 = Bucket(0, 5) + var b3 = Bucket(0, 3) + + var bucketState = BucketState(listOf(b8, b5, b3), 0, 0, null) + + var stateInfos = mutableListOf() + stateInfos.add(bucketState) + search(stateInfos, bucketState, actions) + + PrintResult(stateInfos) +} + +/** + * 桶信息 + */ +class Bucket(var water: Int, var capcity: Int) + +/** + * 求解一系列桶状态 序列 + */ +class BucketState( + var buckets: List, var from: Int, var to: Int, + var parent: BucketState? +) { + fun takeAction(i: Int, j: Int, next: (BucketState?) -> Unit) { + + //穿件一个改变的实体 + var newBucketState = copy() + newBucketState.parent = this + + var fromBucket = newBucketState.buckets[i] + var toBucket = newBucketState.buckets[j] + + if (fromBucket.water == 0) { + next(null) + return + } + + var canAcceptWater = newBucketState.buckets[j].capcity - buckets[j].water + + if (canAcceptWater == 0) { + next(null) + return + } + + if (canAcceptWater <= fromBucket.water) { + toBucket.water = toBucket.capcity + fromBucket.water = fromBucket.water - canAcceptWater + } else { + toBucket.water = toBucket.water + fromBucket.water + fromBucket.water = 0 + } + + next(newBucketState) + } +} + +/** + * 查找所有可能 + */ +fun search( + states: MutableList, + bucketState: BucketState, + actions: Array> +) { + if (IsFinalState(bucketState)) //判断是否到达[4,4,0]状态 + { + println("找到了一种结果") + return + } + actions.forEach { + var next: BucketState? = null + //每次使用新的实体去查找,然后存储 + bucketState.copy().takeAction(it[0], it[1]) { + next = it + } + next?.let { + if (!isDuplicate(states, it)) { + search(states, it, actions) + } + } + } +} + +/** + * 是否之前已经操作出现了的状态避免重复 + */ +fun isDuplicate(states: MutableList, next: BucketState): Boolean { + + if (IsFinalState(next)) { + states.add(next) + return false + } + states.forEach { + if (it.buckets[0].water == next.buckets[0].water + && it.buckets[1].water == next.buckets[1].water + && it.buckets[2].water == next.buckets[2].water + ) { + return true + } + } + states.add(next) + return false +} + +/** + * 打印结果 + */ +fun PrintResult(states: List) { + for (i in states) { + if (IsFinalState(i)) { + printThree(i) + println("${i.from},${i.to} => ${printFormat(i)}") + println() + } + } +} + +private fun printFormat(bucketState:BucketState):String { + return "" + bucketState.buckets[0].water + "-" + bucketState.buckets[1].water + "-" + bucketState.buckets[2].water +} + +/** + * 打印树 + */ +fun printThree(i: BucketState) { + i.parent?.let { + var result = + "" + it.buckets[0].water + "-" + it.buckets[1].water + "-" + it.buckets[2].water + printThree(it) + println("${it.from},${it.to} => " + result) + } + +} + +/** + * 满足 4 4 0 + */ +fun IsFinalState(bucketState: BucketState): Boolean { + if (bucketState.buckets[0].water == 4 && bucketState.buckets[1].water == 4 && bucketState.buckets[2].water == 0) { + return true + } + return false +} + +/** + * 复制新的实体 + */ +fun BucketState.copy(): BucketState { + var b8 = Bucket(this.buckets[0].water, 8) + var b5 = Bucket(this.buckets[1].water, 5) + var b3 = Bucket(this.buckets[2].water, 3) + return BucketState(listOf(b8, b5, b3), from, to, parent) +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionchickenrabbit.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionchickenrabbit.kt new file mode 100644 index 0000000..2fa1527 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/exhaustionchickenrabbit.kt @@ -0,0 +1,25 @@ +package com.wangpos.datastructure.algorithm + + +fun main(args: Array) { + + chickenAndRabbit() +} + +/** + * 鸡的解空间 0 50 + * + * 兔的解空间 0 30 + */ +fun chickenAndRabbit() { + for (rabbit in 0 until 31) { + var max = 50 - rabbit + for (chicken in 0 until max+1) { + var count = chicken * 2 + rabbit* 4 + + if (count == 120 && (chicken + rabbit)==50) { + println("鸡 $chicken 兔 $rabbit") + } + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/algorithm/wolfsheepfood.kt b/app/src/main/java/com/wangpos/datastructure/algorithm/wolfsheepfood.kt new file mode 100644 index 0000000..7da659b --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/algorithm/wolfsheepfood.kt @@ -0,0 +1,17 @@ +package com.wangpos.datastructure.algorithm + +fun main() { + + + +} + + + +class Wolf(left:Int,right:Int) + +class Food(left:Int,right:Int) + +class Sheep(left:Int,right:Int) + +class MoveRecord() \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/android/Intercept.kt b/app/src/main/java/com/wangpos/datastructure/android/Intercept.kt new file mode 100644 index 0000000..3edba14 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/android/Intercept.kt @@ -0,0 +1,60 @@ +package com.wangpos.datastructure.android + +import okhttp3.Interceptor + +abstract class Intercept { + + open fun intercept(chain: Chain):Response?{ + return chain.procced() + } +} + +class Chain(val index: Int, val intercepts: List,val request: Request) { + + fun procced(index: Int, intercepts: List, request: Request):Response? { + if (index < intercepts.size) { + val intercept = intercepts.get(index) + val next = Chain(index+1,intercepts,request) + val response = intercept.intercept(next) + return response + } + + return null + } + + fun procced():Response?{ + return procced(index,intercepts,request) + } +} + +class Response +class Request(var url:String) + +/** + * 时间分发就是这样搞的 + */ +class MyIntercept: Intercept() { + override fun intercept(chain: Chain): Response? { + return super.intercept(chain) + } +} + +fun main() { + + val intercepts = arrayListOf() + + intercepts.add(object: Intercept() { + override fun intercept(chain: Chain): Response? { + chain.request.url = "123" + // 这里不会立即返回,需要等最后一个拦截器执行完,这是一个递归的操作,也可以直接 return null 或者想要的数据 + return super.intercept(chain) + } + + }) + //添加很多拦截器 + val request = Request("hahahah") + val chain = Chain(0, intercepts, request) + chain.procced(0, intercepts, request) +} + + diff --git a/app/src/main/java/com/wangpos/datastructure/android/MeasureTestActivity.java b/app/src/main/java/com/wangpos/datastructure/android/MeasureTestActivity.java new file mode 100644 index 0000000..675f2ac --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/android/MeasureTestActivity.java @@ -0,0 +1,20 @@ +package com.wangpos.datastructure.android; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.Nullable; + +import com.wangpos.datastructure.R; + +/** + * Created by qiyue on 2018/8/1. + */ + +public class MeasureTestActivity extends Activity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.measure_layout); + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/core/USList.java b/app/src/main/java/com/wangpos/datastructure/core/USList.java new file mode 100644 index 0000000..d7fcf38 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/core/USList.java @@ -0,0 +1,22 @@ +package com.wangpos.datastructure.core; + +import com.wangpos.datastructure.BuildConfig; + +import java.util.ArrayList; + +/** + * Created by qiyue on 2018/6/21. + */ + +public class USList extends ArrayList { + + + public boolean add(E e){ +// if(BuildConfig.DEBUG_MODEL) { +// super.add(0, e); +// }else{ + super.add(e); +// } + return true; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/core/WebViewActivity.java b/app/src/main/java/com/wangpos/datastructure/core/WebViewActivity.java index 70966b3..9137838 100755 --- a/app/src/main/java/com/wangpos/datastructure/core/WebViewActivity.java +++ b/app/src/main/java/com/wangpos/datastructure/core/WebViewActivity.java @@ -34,6 +34,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); webview = (WebView) findViewById(R.id.webview); + + initCreateData(); setWindowStatusBarColor(this,R.color.githubtitle); } @@ -131,6 +133,7 @@ protected void initCreateData() { webview.getSettings().setAppCacheEnabled(true);//是否使用缓存 webview.getSettings().setDomStorageEnabled(true);//DOM Storage webview.getSettings().setTextSize(WebSettings.TextSize.NORMAL); + this.webview.loadUrl(this.getUrl()); } /* @SuppressLint("SetJavaScriptEnabled") diff --git a/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraph.java b/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraph.java index 30631ec..5f696c7 100644 --- a/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraph.java +++ b/app/src/main/java/com/wangpos/datastructure/graph/UndirectedGraph.java @@ -145,8 +145,11 @@ public void DFS() { } - - + /** + * 深度优先搜索 + * @param i + * @param visited + */ private void DFS(int i, boolean[] visited) { ENode node; diff --git a/app/src/main/java/com/wangpos/datastructure/interview/DesignV28Activity.java b/app/src/main/java/com/wangpos/datastructure/interview/DesignV28Activity.java new file mode 100644 index 0000000..efd2d59 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/interview/DesignV28Activity.java @@ -0,0 +1,44 @@ +package com.wangpos.datastructure.interview; + +import android.app.Activity; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.chip.Chip; +import android.support.design.chip.ChipGroup; +import android.view.View; + +import com.wangpos.datastructure.R; + +/** + * Created by qiyue on 2018/6/20. + */ + +public class DesignV28Activity extends Activity { + + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_designv28); + + final Chip chip = (Chip)findViewById(R.id.single_chip); + + chip.setOnCloseIconClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + chip.setVisibility(View.INVISIBLE); + } + }); + + ChipGroup chipGroup = (ChipGroup)findViewById(R.id.chipGroup); + chipGroup.setOnCheckedChangeListener(new ChipGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(ChipGroup group, int checkedId) { + group.setVisibility(View.INVISIBLE); + } + }); + + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/AddTwoNumber.java b/app/src/main/java/com/wangpos/datastructure/java/AddTwoNumber.java index 9abd56a..d74f52f 100644 --- a/app/src/main/java/com/wangpos/datastructure/java/AddTwoNumber.java +++ b/app/src/main/java/com/wangpos/datastructure/java/AddTwoNumber.java @@ -3,6 +3,11 @@ import com.wangpos.datastructure.R; import com.wangpos.datastructure.core.BaseActivity; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + /** * Created by qiyue on 2017/12/7. */ @@ -52,7 +57,8 @@ protected String getSummaryData() { public ListNode addTwoNumber(ListNode l1,ListNode l2){ - + CopyOnWriteArrayList list = new CopyOnWriteArrayList(); + ConcurrentHashMap map = new ConcurrentHashMap(10); return null; } diff --git a/app/src/main/java/com/wangpos/datastructure/java/ArrayListActivity.java b/app/src/main/java/com/wangpos/datastructure/java/ArrayListActivity.java new file mode 100644 index 0000000..0a9941f --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/ArrayListActivity.java @@ -0,0 +1,49 @@ +package com.wangpos.datastructure.java; + +import com.wangpos.datastructure.core.BaseActivity; + +/** + * Created by qiyue on 2018/6/15. + */ + +public class ArrayListActivity extends BaseActivity { + @Override + protected void initData() { + + } + + @Override + protected String getTextData() { + return null; + } + + @Override + protected int getImageData() { + return 0; + } + + @Override + protected String getResultData() { + return null; + } + + @Override + protected String getTimeData() { + return null; + } + + @Override + protected String getSpaceTimeData() { + return null; + } + + @Override + protected String getWendingXingData() { + return null; + } + + @Override + protected String getSummaryData() { + return null; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/CJThreadPool.java b/app/src/main/java/com/wangpos/datastructure/java/CJThreadPool.java new file mode 100644 index 0000000..a60401d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/CJThreadPool.java @@ -0,0 +1,97 @@ +package com.wangpos.datastructure.java; + +import android.util.Log; + +import com.wangpos.datastructure.java.thread.ThreadExcutor; + +import java.util.HashSet; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Created by qiyue on 2018/6/25. + */ + +public class CJThreadPool { + + //创建 + private volatile boolean RUNNING = true; + + LinkedBlockingQueue blockingQueues; + + private final static int DEFAULT_CORESIZE = 3; + + private final static int DEFAULT_QUEUESIZE = 10; + + + HashSet works = new HashSet<>(); + + private int coreSize = DEFAULT_CORESIZE; + + private int queueSize = DEFAULT_QUEUESIZE; + + private int currentStartThreadSize = 0; + boolean shutdown = false; + + + public CJThreadPool() { + this(DEFAULT_CORESIZE, DEFAULT_QUEUESIZE); + } + + public CJThreadPool(int a_coreSize, int a_queueSize) { + this.coreSize = a_coreSize; + this.blockingQueues = new LinkedBlockingQueue(a_queueSize); + Log.i("qy","create"); + } + + public void execute(Runnable a_runnable) { + if (currentStartThreadSize < coreSize) { + currentStartThreadSize++; +// a_runnable.run(); +// Log.i("qy", "add" + currentStartThreadSize); + WorkThread workThread = new WorkThread(); + workThread.start(); + works.add(workThread); + } + + try { +// Log.i("qy","put-1==="+blockingQueues.size()); + blockingQueues.put(a_runnable); +// Log.i("qy","put-2===="+blockingQueues.size()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + class WorkThread extends Thread { + @Override + public void run() { + super.run(); + + while (true && RUNNING) { + try { + + Runnable current = blockingQueues.take(); + current.run(); + Log.i("qy", "我消费了一个" + Thread.currentThread().getName() + "====" + current + " size = " + blockingQueues.size()); + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + + } + + public void shutdown() { + RUNNING = false; + + works.clear(); + blockingQueues.clear(); + } + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/DeadLockDemo.java b/app/src/main/java/com/wangpos/datastructure/java/DeadLockDemo.java new file mode 100644 index 0000000..9db8cee --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/DeadLockDemo.java @@ -0,0 +1,54 @@ +package com.wangpos.datastructure.java; + +/** + * Created by qiyue on 2018/6/15. + */ + +public class DeadLockDemo { + + private Object lock1 = new Object(); + private Object lock2 = new Object(); + + + public void startAllThread(){ + new Thread_One().start(); + new Thread_Two().start(); + } + + class Thread_One extends Thread{ + + @Override + public void run() { + super.run(); + synchronized (lock1){ + try { + sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + synchronized (lock2){ + + System.out.println("线程 1在执行"); + } + } + } + } + + class Thread_Two extends Thread{ + @Override + public void run() { + super.run(); + synchronized (lock2){ + try { + sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + synchronized (lock1){ + + System.out.println("线程 2在执行"); + } + } + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/JavaThreadActivity.java b/app/src/main/java/com/wangpos/datastructure/java/JavaThreadActivity.java index 1e99025..19d770b 100644 --- a/app/src/main/java/com/wangpos/datastructure/java/JavaThreadActivity.java +++ b/app/src/main/java/com/wangpos/datastructure/java/JavaThreadActivity.java @@ -195,8 +195,8 @@ protected void onCreate(Bundle savedInstanceState) { // t2.start(); - codeView2.showCode(" Person person = new Person(\"AAAAA\");\n" + - " Person person2 = new Person(\"YYYYYY\");\n" + + codeView2.showCode(" Person_E person = new Person_E(\"AAAAA\");\n" + + " Person_E person2 = new Person_E(\"YYYYYY\");\n" + " TestObjectLockThread t3 = new TestObjectLockThread(person);\n" + " TestObjectLockThread t4 = new TestObjectLockThread(person2);\n" + " t3.start();\n" + diff --git a/app/src/main/java/com/wangpos/datastructure/java/JavaThreadPrincipleActivity.java b/app/src/main/java/com/wangpos/datastructure/java/JavaThreadPrincipleActivity.java new file mode 100644 index 0000000..b073e43 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/JavaThreadPrincipleActivity.java @@ -0,0 +1,197 @@ +package com.wangpos.datastructure.java; + +import android.util.Log; +import android.view.View; + +import com.wangpos.datastructure.core.BaseActivity; +import com.wangpos.datastructure.core.CodeBean; +import com.wangpos.datastructure.java.thread.ThreadExcutor; +import com.wangpos.datastructure.java.thread.USThreadPool; + +import org.jsoup.Connection; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Created by qiyue on 2018/6/14. + */ + +public class JavaThreadPrincipleActivity extends BaseActivity { + + private ThreadExcutor threadExcutor; + CJThreadPool cjThreadPool; + + @Override + protected void initData() { + + addItem(new CodeBean("自己实现简单的线程池原理" ,threadcode)); + + cjThreadPool = new CJThreadPool(); + + + runBtn.setVisibility(View.VISIBLE); + runBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + for (int i = 0; i < 10; i++) { + final int finalI = i; + cjThreadPool.execute(new Runnable() { + @Override + public void run() { + Log.i("qy","I am "+"("+ System.currentTimeMillis() +")"); + } + }); + } + } + }); + + + } + + + @Override + protected void onPause() { + super.onPause(); +// threadExcutor.shutdown(); + + cjThreadPool.shutdown(); + + } + + @Override + protected String getTextData() { + return "开启10个测试线程"; + } + + @Override + protected int getImageData() { + return 0; + } + + @Override + protected String getResultData() { + return null; + } + + @Override + protected String getTimeData() { + return null; + } + + @Override + protected String getSpaceTimeData() { + return null; + } + + @Override + protected String getWendingXingData() { + return null; + } + + @Override + protected String getSummaryData() { + return null; + } + + + + public static String threadcode = "package com.wangpos.datastructure.java;\n" + + "\n" + + "import android.util.Log;\n" + + "\n" + + "import com.wangpos.datastructure.java.thread.ThreadExcutor;\n" + + "\n" + + "import java.util.HashSet;\n" + + "import java.util.concurrent.ArrayBlockingQueue;\n" + + "import java.util.concurrent.BlockingQueue;\n" + + "import java.util.concurrent.LinkedBlockingQueue;\n" + + "\n" + + "/**\n" + + " * Created by qiyue on 2018/6/25.\n" + + " */\n" + + "\n" + + "public class CJThreadPool {\n" + + "\n" + + " //创建\n" + + " private volatile boolean RUNNING = true;\n" + + "\n" + + " LinkedBlockingQueue blockingQueues;\n" + + "\n" + + " private final static int DEFAULT_CORESIZE = 3;\n" + + "\n" + + " private final static int DEFAULT_QUEUESIZE = 10;\n" + + "\n" + + "\n" + + " HashSet works = new HashSet<>();\n" + + "\n" + + " private int coreSize = DEFAULT_CORESIZE;\n" + + "\n" + + " private int queueSize = DEFAULT_QUEUESIZE;\n" + + "\n" + + " private int currentStartThreadSize = 0;\n" + + " boolean shutdown = false;\n" + + "\n" + + "\n" + + " public CJThreadPool() {\n" + + " this(DEFAULT_CORESIZE, DEFAULT_QUEUESIZE);\n" + + " }\n" + + "\n" + + " public CJThreadPool(int a_coreSize, int a_queueSize) {\n" + + " this.coreSize = a_coreSize;\n" + + " this.blockingQueues = new LinkedBlockingQueue(a_queueSize);\n" + + " Log.i(\"qy\",\"create\");\n" + + " }\n" + + "\n" + + " public void execute(Runnable a_runnable) {\n" + + " if (currentStartThreadSize < coreSize) {\n" + + " currentStartThreadSize++;\n" + + "// a_runnable.run();\n" + + "// Log.i(\"qy\", \"add\" + currentStartThreadSize);\n" + + " WorkThread workThread = new WorkThread();\n" + + " workThread.start();\n" + + " works.add(workThread);\n" + + " }\n" + + "\n" + + " try {\n" + + "// Log.i(\"qy\",\"put-1===\"+blockingQueues.size());\n" + + " blockingQueues.put(a_runnable);\n" + + "// Log.i(\"qy\",\"put-2====\"+blockingQueues.size());\n" + + " } catch (InterruptedException e) {\n" + + " e.printStackTrace();\n" + + " }\n" + + " }\n" + + "\n" + + "\n" + + " class WorkThread extends Thread {\n" + + " @Override\n" + + " public void run() {\n" + + " super.run();\n" + + "\n" + + " while (true && RUNNING) {\n" + + " try {\n" + + "\n" + + " Runnable current = blockingQueues.take();\n" + + " current.run();\n" + + " Log.i(\"qy\", \"我消费了一个\" + Thread.currentThread().getName() + \"====\" + current + \" size = \" + blockingQueues.size());\n" + + " Thread.sleep(1000);\n" + + " } catch (InterruptedException e) {\n" + + " e.printStackTrace();\n" + + " }\n" + + " }\n" + + " }\n" + + "\n" + + "\n" + + " }\n" + + "\n" + + " public void shutdown() {\n" + + " RUNNING = false;\n" + + "\n" + + " works.clear();\n" + + " blockingQueues.clear();\n" + + " }\n" + + "\n" + + "\n" + + "}\n"; +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/LockTestActivity.java b/app/src/main/java/com/wangpos/datastructure/java/LockTestActivity.java new file mode 100644 index 0000000..6bc0e91 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/LockTestActivity.java @@ -0,0 +1,99 @@ +package com.wangpos.datastructure.java; + +import android.util.Log; + +import com.wangpos.datastructure.core.BaseActivity; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by qiyue on 2018/7/18. + */ + +public class LockTestActivity extends BaseActivity { + + public static String TAG = LockTestActivity.class.getSimpleName(); + + @Override + protected void initData() { + + LockTestThread t1 = new LockTestThread(); + LockTestThread t2 = new LockTestThread(); + t1.start(); + t2.start(); + + } + + @Override + protected String getTextData() { + return null; + } + + @Override + protected int getImageData() { + return 0; + } + + @Override + protected String getResultData() { + return null; + } + + @Override + protected String getTimeData() { + return null; + } + + @Override + protected String getSpaceTimeData() { + return null; + } + + @Override + protected String getWendingXingData() { + return null; + } + + @Override + protected String getSummaryData() { + return null; + } + + + + private Lock lock = new ReentrantLock(); + + + class LockTestThread extends Thread{ + + + @Override + public void run() { + super.run(); + + + if (lock.tryLock()) { + try { + lock.lock(); + + Log.i(TAG, "线程名" + this.getName() + "获得了锁"); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + lock.unlock(); + Log.i(TAG, "线程名" + this.getName() + "释放了锁"); + } + }else{ + Log.i(TAG, "线程名" + this.getName() + "有人占用锁"); + + } + + + + + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/ReferenceQueueActivity.java b/app/src/main/java/com/wangpos/datastructure/java/ReferenceQueueActivity.java new file mode 100644 index 0000000..1c6d618 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/ReferenceQueueActivity.java @@ -0,0 +1,105 @@ +package com.wangpos.datastructure.java; + +import android.util.Log; +import android.widget.BaseAdapter; + +import com.wangpos.datastructure.core.BaseActivity; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +/** + * Created by qiyue on 2018/6/11. + */ + +public class ReferenceQueueActivity extends BaseActivity { + private static String TAG = ReferenceQueueActivity.class.getSimpleName(); + int _1M = 1024*1024*2; + @Override + protected void initData() { + final ReferenceQueue referenceQueue = new ReferenceQueue(); + Object value = new Object(); + final Map map = new HashMap<>(); + Thread thread = new Thread() { + @Override + public void run() { + super.run(); + + try { + int cnt = 0; + WeakR k; + while((k = (WeakR) referenceQueue.remove()) != null) { + Log.i(TAG,(cnt++) + "回收了:" + k); + map.remove(k.key); + Log.i(TAG,"map.size->" + map.size()); + } + } catch(InterruptedException e) { + //结束循环 + } + } + }; + thread.setDaemon(true); + thread.start(); + + + for(int i = 0;i < 10;i++) { + byte[] bytesKey = new byte[_1M]; + byte[] bytesValue = new byte[_1M]; + map.put(bytesKey, new WeakR(bytesKey, bytesValue, referenceQueue)); + } + Log.i(TAG,"insert finish" + map.size()); + +// Map map1 = new HashMap(); +// for(int i = 0;i<100;i++){ +// byte[] bytes = new byte[_1M]; +//// WeakReference weakReference = new WeakReference(bytes, referenceQueue); +// map1.put(bytes, value); +// } + } + + @Override + protected String getTextData() { + return null; + } + + @Override + protected int getImageData() { + return 0; + } + + @Override + protected String getResultData() { + return null; + } + + @Override + protected String getTimeData() { + return null; + } + + @Override + protected String getSpaceTimeData() { + return null; + } + + @Override + protected String getWendingXingData() { + return null; + } + + @Override + protected String getSummaryData() { + return null; + } + + + class WeakR extends WeakReference { + private Object key; + WeakR(Object key, byte[] referent, ReferenceQueue q) { + super(referent, q); + this.key = key; + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/ThreadOneZero.java b/app/src/main/java/com/wangpos/datastructure/java/ThreadOneZero.java new file mode 100644 index 0000000..ee4da50 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/ThreadOneZero.java @@ -0,0 +1,209 @@ +package com.wangpos.datastructure.java; + +import android.util.Log; + +/** + * Created by qiyue on 2018/6/16. + */ + +public class ThreadOneZero { + + + public static void testPrint(){ + CustomLock lock = new CustomLock(); + + Thread zero_thread = new PrintThreadZero(lock); + Thread one_thread = new PrintThreadOne(lock); + + one_thread.start(); + zero_thread.start(); + + } + + //自定义锁,通过标识位设置优先打印顺序,这样就和两个线程启动顺序无关了 + public static class CustomLock { + public boolean isPriorityPrintZero = true; + } + + + static class PrintThreadZero extends Thread{ + + private CustomLock lock; + + public PrintThreadZero(CustomLock llock){ + this.lock = llock; + } + + @Override + public void run() { + super.run(); + for(int i=0;i<=1000;i++){ +// System.out.println("0——————————————————00"); + synchronized (lock){ +// System.out.println("0——————————————————11"); + if (lock.isPriorityPrintZero){ + System.out.println("0"); + lock.isPriorityPrintZero = false; + lock.notify(); +// System.out.println("0——————————————————22"); + } + + try { +// System.out.println("0——————————————————33"); + lock.wait(); +// System.out.println("0——————————————————44"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + } + + static class PrintThreadOne extends Thread{ + + private CustomLock lock; + + public PrintThreadOne(CustomLock llock){ + this.lock = llock; + } + @Override + public void run() { + super.run(); + for(int i=0;i<1000;i++){ +// System.out.println("1——————————————————00"); + synchronized (lock){ +// System.out.println("1——————————————————11"); + if (!lock.isPriorityPrintZero){ + System.out.println("1"); + lock.isPriorityPrintZero = true; + lock.notify(); +// System.out.println("1——————————————————22"); + } + + try { +// System.out.println("1——————————————————33"); + lock.wait(); +// System.out.println("1——————————————————44"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + } + +} + +/* + +0——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 + +1——————————————————00 +1——————————————————11 +1 +1——————————————————22 +1——————————————————33 + +0——————————————————44 //第一次被wait处后面被执行 + +0——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 + +1——————————————————44 //第二次被wait处后面被执行 + + +1——————————————————00 +1——————————————————11 +1 +1——————————————————22 +1——————————————————33 + +0——————————————————44 // + +0——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 +1——————————————————44// + + */ + + +/* +0——————————————————00 +1——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 +1——————————————————11 +1 +1——————————————————22 +1——————————————————33 +0——————————————————44 +0——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 +1——————————————————44 +1——————————————————00 +1——————————————————11 +1 +1——————————————————22 +1——————————————————33 +0——————————————————44 +0——————————————————00 +0——————————————————11 + */ + + + +/* +1——————————————————00 +1——————————————————11 +0——————————————————00 +1——————————————————33 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 +1——————————————————44 +1——————————————————00 +1——————————————————11 +1 +1——————————————————22 +1——————————————————33 +0——————————————————44 +0——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 +1——————————————————44 +1——————————————————00 +1——————————————————11 +1 +1——————————————————22 +1——————————————————33 +0——————————————————44 +0——————————————————00 +0——————————————————11 +0 +0——————————————————22 +0——————————————————33 +1——————————————————44 +1——————————————————00 +1——————————————————11 +1 +1——————————————————22 + */ \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/condition/BoundedBuffer.kt b/app/src/main/java/com/wangpos/datastructure/java/condition/BoundedBuffer.kt new file mode 100644 index 0000000..a912e0b --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/condition/BoundedBuffer.kt @@ -0,0 +1,66 @@ +package com.wangpos.datastructure.java.condition + +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.locks.Lock +import java.util.concurrent.locks.Condition +import java.util.concurrent.locks.ReentrantLock +import java.util.concurrent.locks.ReentrantReadWriteLock + +internal class BoundedBuffer { + val lock: Lock = ReentrantLock() + val notFull = lock.newCondition() + val notEmpty = lock.newCondition() + + val items = arrayOfNulls(5) + var putptr: Int = 0 + var takeptr: Int = 0 + var count: Int = 0 + + @Throws(InterruptedException::class) + fun put(x: Any) { + lock.lock() //获取锁 + try { + // 如果“缓冲已满”,则等待;直到“缓冲”不是满的,才将x添加到缓冲中。 + while (count == items.size) + notFull.await() + // 将x添加到缓冲中 + items[putptr] = x + // 将“put统计数putptr+1”;如果“缓冲已满”,则设putptr为0。 + if (++putptr == items.size) putptr = 0 + // 将“缓冲”数量+1 + ++count + // 唤醒take线程,因为take线程通过notEmpty.await()等待 + notEmpty.signal() + + // 打印写入的数据 + println(Thread.currentThread().name + " put " + x as Int) + } finally { + lock.unlock() // 释放锁 + } + } + + @Throws(InterruptedException::class) + fun take(): Any { + lock.lock() //获取锁 + try { + // 如果“缓冲为空”,则等待;直到“缓冲”不为空,才将x从缓冲中取出。 + while (count == 0) + notEmpty.await() + // 将x从缓冲中取出 + val x = items[takeptr] + // 将“take统计数takeptr+1”;如果“缓冲为空”,则设takeptr为0。 + if (++takeptr == items.size) takeptr = 0 + // 将“缓冲”数量-1 + --count + // 唤醒put线程,因为put线程通过notFull.await()等待 + notFull.signal() + + // 打印取出的数据 + println(Thread.currentThread().name + " take " + x as Int) + return x + } finally { + lock.unlock() // 释放锁 + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/condition/ConditionDemo.kt b/app/src/main/java/com/wangpos/datastructure/java/condition/ConditionDemo.kt new file mode 100644 index 0000000..33e16a7 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/condition/ConditionDemo.kt @@ -0,0 +1,76 @@ +package com.wangpos.datastructure.java.condition + +import java.util.concurrent.locks.ReentrantLock + + + +val lock = ReentrantLock() +val condition = lock.newCondition() + + +private val bb = BoundedBuffer() + +fun main(args:Array){ +// var ta = ThreadA("A") +// +// lock.lock() // 获取锁 +// try { +// println(Thread.currentThread().name + " start ta") +// ta.start() +// +// println(Thread.currentThread().name + " block") +// condition.await() // 等待 释放锁 +// +// println(Thread.currentThread().name + " continue") +// } catch (e: InterruptedException) { +// e.printStackTrace() +// } finally { +// lock.unlock() // 释放锁 +// } + + + // 启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9); + // 启动10个“读线程”,从BoundedBuffer中不断的读数据。 + for (i in 0..9) { + PutThread("p$i", i).start() + TakeThread("t$i").start() + } + + +} + + +internal class PutThread(name: String, private val num: Int) : Thread(name) { + override fun run() { + try { + Thread.sleep(1) // 线程休眠1ms + bb.put(num) // 向BoundedBuffer中写入数据 + } catch (e: InterruptedException) { + } + + } +} + +internal class TakeThread(name: String) : Thread(name) { + override fun run() { + try { + Thread.sleep(10) // 线程休眠1ms + val num = bb.take() as Int // 从BoundedBuffer中取出数据 + } catch (e: InterruptedException) { + } + + } +} + +internal class ThreadA(name: String) : Thread(name) { + + override fun run() { + lock.lock() // 获取锁 + try { + println(Thread.currentThread().name + " wakup others") + condition.signal() // 唤醒“condition所在锁上的其它线程” + } finally { + lock.unlock() // 释放锁 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/condition/TestLinkBlockQueue.kt b/app/src/main/java/com/wangpos/datastructure/java/condition/TestLinkBlockQueue.kt new file mode 100644 index 0000000..4927f20 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/condition/TestLinkBlockQueue.kt @@ -0,0 +1,55 @@ +package com.wangpos.datastructure.java.condition + +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.thread + + +fun main() { + + + var blockingQueue = LinkedBlockingQueue(3) + + var atomicoInteger = AtomicInteger(); + + + var lock = ReentrantLock() + var condition = lock.newCondition() + + var oneThread = thread { + // blockingQueue.put("test-$i") + var count = atomicoInteger.get() + + lock.lock() + while (atomicoInteger.get() == 0) { + println("----------oneThread---------- ") + condition.await() + println("---------222-oneThread---------- ") + } + lock.unlock() + } + + var threeThread = thread { + for (i in 1..12) { + println(">>>>>>>>threeThread>>>>>>>>> $i") + + atomicoInteger.getAndIncrement() + } + condition.signal() + } + +/* var twoThread = thread { + + // println("read = "+blockingQueue.take()) + var count = atomicoInteger.get() + while (count > 10) { + for (i in 0..10) { + println(">>>>>>>>>>>>>>>>> $i") + atomicoInteger.getAndDecrement() + } + } + }*/ + oneThread.start() + threeThread.start() +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/mylist/CJArrayList.java b/app/src/main/java/com/wangpos/datastructure/java/mylist/CJArrayList.java new file mode 100644 index 0000000..259fe2d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/mylist/CJArrayList.java @@ -0,0 +1,101 @@ +package com.wangpos.datastructure.java.mylist; + +import java.util.Arrays; +import java.util.Collection; + +/** + * Created by qiyue on 2018/6/19. + * ArrayList 简易版 来讲述基本原理 + */ + +public class CJArrayList { + + transient Object[] elementData; // non-private to simplify nested class access + + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + * 要分配的最大数组大小。 + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * 默认初始化容量 + */ + private static final int DEFAULT_CAPACITY = 10; + + private int size; + + /** + * 默认创建一个空的数组 + */ + public CJArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * 向集合添加数据 + */ + public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e;//size++ 表示真实数据的size + return true; + } + + /** + * 最少要达到的容量,否者溢出 + * @param minCapacity + */ + private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } + + private void ensureExplicitCapacity(int minCapacity) { +// modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0)//minCapacity当前需要的容量是否超过数组最大值 + grow(minCapacity); + } + + + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1);//增加1一半 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity;//默认一般的扩容还是小于minCapacity 就用minCapacity + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity);//超出MA_ARRAY_SIZE 重新计算 + elementData = Arrays.copyOf(elementData, newCapacity);//创建新的数组并将原数据拷贝 + } + + + + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + + public int size() { + return size; + } + + public E get(int index) { + if (index >= size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + + return (E) elementData[index]; + } + + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+size; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/mylist/CJLinkList.java b/app/src/main/java/com/wangpos/datastructure/java/mylist/CJLinkList.java new file mode 100644 index 0000000..a24f8a4 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/mylist/CJLinkList.java @@ -0,0 +1,190 @@ +package com.wangpos.datastructure.java.mylist; + +import java.util.LinkedList; + +/** + * Created by qiyue on 2018/6/20. + */ + +public class CJLinkList { + + + transient int size = 0; + //头指针 + transient Node first; + + //尾指针 + transient Node last; + + + public CJLinkList() { + } + + /** + * 双链表每个节点包括 前驱、后继、数据 + * 构造函数也直接对3个数据进行赋值 + * @param + */ + 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; + } + } + + + public boolean add(E e) { + linkLast(e); + return true; + } + + /** + * 链接到尾部 + * @param e + */ + void linkLast(E e) { + final Node l = last; + final Node newNode = new Node<>(l, e, null);//封装新的Node + last = newNode;//改变最后元素指针 + if (l == null) + first = newNode; + else + l.next = newNode;//与前面链子链接 + size++; + } + + + public E get(int index) { + checkElementIndex(index); + return node(index).item; + } + + Node node(int index) { + // assert isElementIndex(index); + + /** + * 判断数据的是在前半部分,还是后半部分,决定查找方法 + */ + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + + /** + * 检查index 是否在size范围 + * @param index + */ + private void checkElementIndex(int index) { + if (!isElementIndex(index)) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + private boolean isElementIndex(int index) { + return index >= 0 && index < size; + } + + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+size; + } + + /** + * 判断集合是否包含元素 + * @param o + * @return + */ + public boolean contains(Object o) { + return indexOf(o) != -1; + } + + /** + * 查找数据的位置第一次出现的位置,找不到返回-1 + * @param o + * @return + */ + public int indexOf(Object o) { + int index = 0; + if (o == null) {//当数据是null的时候,下面方式查找 + for (Node x = first; x != null; x = x.next) { + if (x.item == null) + return index; + index++; + } + } else {// 当数据不为null时,调用对象本身equals进行比较 + for (Node x = first; x != null; x = x.next) { + if (o.equals(x.item)) + return index; + index++; + } + } + return -1; + } + + public boolean remove(Object o) { + if (o == 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(x); + return true; + } + } + } + return false; + } + + /** + * 断开非空元素 + * + * |prev|dataX|next| |prev|dataY|next| |prev|dataZ|next| + * + * 断开 dataY 数据,需要改变 dataX 的后继指向,和dataZ的前驱指向,同时自身的前驱后继要指向null 这才算完全断开 + */ + 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) {//前驱为null,证明当前是first,所以,first指向next即可 + first = next; + } else { + prev.next = next;//前驱不为null,前驱的next指向next,此时自己的前驱指向为null + x.prev = null; + } + /** + * 将后继断开并,配置后继的前驱指向 + */ + if (next == null) { + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + return element; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/semaphore/TestSemaphore.kt b/app/src/main/java/com/wangpos/datastructure/java/semaphore/TestSemaphore.kt new file mode 100644 index 0000000..5a11d7d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/semaphore/TestSemaphore.kt @@ -0,0 +1,7 @@ +package com.wangpos.datastructure.java.semaphore + + +fun main() { + + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/thread/Helper.kt b/app/src/main/java/com/wangpos/datastructure/java/thread/Helper.kt new file mode 100644 index 0000000..cf36c03 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/thread/Helper.kt @@ -0,0 +1,58 @@ +package com.wangpos.datastructure.java.thread + +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + * Created by Edison Xu on 2017/3/2. + */ +enum class Helper { + + instance; + + fun run(r: Runnable) { + tPool.submit(r) + } + + fun shutdown() { + tPool.shutdown() + } + + companion object { + + private val tPool = Executors.newFixedThreadPool(2) + + fun buildNoArr(max: Int): Array { + val noArr = arrayOfNulls(max) + for (i in 0 until max) { + noArr[i] = Integer.toString(i + 1) + } + return noArr + } + + fun buildCharArr(max: Int): Array { + val charArr = arrayOfNulls(max) + val tmp = 65 + for (i in 0 until max) { + charArr[i] = (tmp + i).toChar().toString() + } + return charArr + } + + @JvmStatic + fun printString(input: String) { + input?.let { + print(input) + } + } + + @JvmStatic + fun print2String(input: String, input2: String) { + if (input == null) + return + print(input) + print(input2) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadCommunication.kt b/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadCommunication.kt new file mode 100644 index 0000000..c270c30 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadCommunication.kt @@ -0,0 +1,36 @@ +package com.wangpos.datastructure.java.thread + +import kotlin.concurrent.thread + +fun main() { + + val messageBridge = MessageBridge() + + thread(true) { + println(messageBridge.doSomeThing()) + } + thread(true) { + println(messageBridge.doSomeThing2()) + + } + +} + + +class MessageBridge { + + var message: Int = 0 + + @Synchronized + fun doSomeThing(): Int { + message++ + return message + } + + @Synchronized + fun doSomeThing2(): Int { + message++ + return message + } +} + diff --git a/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadCommunication2.kt b/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadCommunication2.kt new file mode 100644 index 0000000..62e59f8 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadCommunication2.kt @@ -0,0 +1,70 @@ +package com.wangpos.datastructure.java.thread + +import java.util.* + + +fun main() { + val one = MethodOne() + Helper.instance.run(one.newThreadOne()) + Helper.instance.run(one.newThreadTwo()) + Helper.instance.shutdown() +} + +/*8 +1. 第一种解法,包含多种小的不同实现方式,但一个共同点就是靠一个共享变量来做控制; +a. 利用最基本的synchronized、notify、wait: + */ + + + +class MethodOne { + private val threadToGo = ThreadToGo() + + fun newThreadOne(): Runnable { + val inputArr = Helper.buildNoArr(52) + println(Arrays.toString(inputArr)) + return Runnable { + try { + var i = 0 + while (i < inputArr.size) { + synchronized(threadToGo) { + while (threadToGo.value == 2) + threadToGo.wait() + Helper.print2String(inputArr[i]!!, inputArr[i + 1]!!) + threadToGo.value = 2 + threadToGo.notify() + } + i += 2 + } + } catch (e: InterruptedException) { + println("Oops...") + } + } + } + + fun newThreadTwo(): Runnable { + val inputArr = Helper.buildCharArr(26) + println(Arrays.toString(inputArr)) + return Runnable { + try { + for (i in inputArr.indices) { + synchronized(threadToGo) { + while (threadToGo.value == 1) + threadToGo.wait() + Helper.printString(inputArr[i]!!) + threadToGo.value = 1 + threadToGo.notify() + } + } + } catch (e: InterruptedException) { + println("Oops...") + } + } + } + + internal inner class ThreadToGo:java.lang.Object() { + var value = 1 + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadExcutor.java b/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadExcutor.java new file mode 100644 index 0000000..77b3c17 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/thread/ThreadExcutor.java @@ -0,0 +1,109 @@ +package com.wangpos.datastructure.java.thread; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public class ThreadExcutor{ + + //创建 + private volatile boolean RUNNING = true; + + //所有任务都放队列中,让工作线程来消费 + private static BlockingQueue queue = null; + + private final HashSet workers = new HashSet(); + + private final List threadList = new ArrayList(); + + //工作线程数 + int poolSize = 0; + //核心线程数(创建了多少个工作线程) + int coreSize = 0; + + boolean shutdown = false; + + public ThreadExcutor(int poolSize){ + this.poolSize = poolSize; + queue = new LinkedBlockingQueue(20); + } + + public void exec(Runnable runnable) { + if (runnable == null) throw new NullPointerException(); + if(coreSize < poolSize){ + addThread(runnable); + }else{ + //System.out.println("offer" + runnable.toString() + " " + queue.size()); + try { + Log.i("qy","put"); + queue.put(runnable); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void addThread(Runnable runnable){ + coreSize ++; + Worker worker = new Worker(runnable); + workers.add(worker); + Thread t = new Thread(worker); + threadList.add(t); + try { + t.start(); + }catch (Exception e){ + e.printStackTrace(); + } + + } + + public void shutdown() { + RUNNING = false; + if(!workers.isEmpty()){ + for (Worker worker : workers){ + worker.interruptIfIdle(); + } + } + shutdown = true; + Thread.currentThread().interrupt(); + } + //这里留个位置放内部类Worker + + class Worker implements Runnable{ + + public Worker(Runnable runnable){ + queue.offer(runnable); + } + + @Override + public void run() { + while (true && RUNNING){ + if(shutdown == true){ + Thread.interrupted(); + } + Runnable task = null; + try { + task = getTask(); + task.run(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public Runnable getTask() throws InterruptedException { + return queue.take(); + } + + public void interruptIfIdle() { + for (Thread thread :threadList) { + System.out.println(thread.getName() + " interrupt"); + thread.interrupt(); + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/java/thread/USThreadPool.java b/app/src/main/java/com/wangpos/datastructure/java/thread/USThreadPool.java new file mode 100644 index 0000000..916c7a9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/thread/USThreadPool.java @@ -0,0 +1,63 @@ +package com.wangpos.datastructure.java.thread; + +import android.util.Log; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * Created by qiyue on 2018/6/14. + */ + +public class USThreadPool { + + private static Set threadSet = new HashSet<>(); + + private static Queue queue = new LinkedList<>(); + + private static final String TAG = USThreadPool.class.getSimpleName(); + + private static volatile USThreadPool instance; + + private USThreadPool(int size) { + + for (int i = 0; i < size; i++) { + WorkThread workThread = new WorkThread(); + workThread.start(); + threadSet.add(workThread); + } + } + + public static USThreadPool getInstance() { + if (instance == null) { + synchronized (USThreadPool.class) { + instance = new USThreadPool(5); + } + } + return instance; + } + + public synchronized void submit(Runnable runnable) { + queue.add(runnable); + } + + + class WorkThread extends Thread { + @Override + public void run() { + super.run(); + + while (true) { + if (!queue.isEmpty()) { + Log.i(TAG, "Thread-id2=" + getId()); + Runnable runnable = queue.poll(); + Log.i(TAG, "Thread-id3=" + getId()); + runnable.run(); + } + } + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/java/thread/automizationoperation.kt b/app/src/main/java/com/wangpos/datastructure/java/thread/automizationoperation.kt new file mode 100644 index 0000000..dc3b682 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/java/thread/automizationoperation.kt @@ -0,0 +1,49 @@ +package com.wangpos.datastructure.java.thread + +import java.util.* + + +fun main() { + test() +} + +fun test() { + + var list = Vector() + for (i in 0..9999) { + list.add("string$i") + } + + Thread(Runnable { + while (true) { + if (list.size > 0) { + val content = list.get(list.size - 1) + } else { + break + } + } + }).start() + + Thread(Runnable { + while (true) { + if (list.size <= 0) { + break + } + list.removeAt(0) + try { + Thread.sleep(10) + } catch (e: InterruptedException) { + e.printStackTrace() + } + + } + }).start() + + + /** + * Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9999 + at java.util.Vector.get(Vector.java:748) + at com.wangpos.datastructure.java.thread.AutomizationoperationKt$test$1.run(automizationoperation.kt:23) + at java.lang.Thread.run(Thread.java:745) + */ +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/ArrayTest.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/ArrayTest.kt new file mode 100644 index 0000000..20e4d74 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/ArrayTest.kt @@ -0,0 +1,61 @@ +package com.wangpos.datastructure.leetcode + + +/** + * 数组和字符串 + * + * 数组的优缺点 + * 能够在O(1)的情况查询一个元素 + * 构建必须是连续的空间 + * 删除和添加某一个元素必须遍历整个表O(n) + */ +fun main() { + println("hello World") + + val data = "helloWorld" + + val result = reverseString(data) + val result2 = reverseCharArray(data) + println(result) + println(result2) +} + +/** + * 反转一个字符串 + */ +fun reverseString(data: String): String { + var headerIndex = 0 + var footerIndex = data.length - 1 + val dataArray = data.toCharArray() + + while (headerIndex != footerIndex && headerIndex < footerIndex) { + swap(headerIndex, footerIndex, dataArray) + headerIndex++ + footerIndex-- + } + val a = dataArray.toString() + var result = "" + for(i in 0 until dataArray.size){ + result += dataArray[i] + } + return result +} + +fun swap(headerIndex: Int, footerIndex: Int, dataArray: CharArray) { + val tempData = dataArray[footerIndex] + dataArray[footerIndex] = dataArray[headerIndex] + dataArray[headerIndex] = tempData +} + + +fun reverseCharArray(data: String):String { + val array = data.toCharArray() + var reverse = "" + for (i in array.size - 1 downTo 0) { + reverse += array[i] + } + + return reverse +} + + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1.kt new file mode 100644 index 0000000..772d021 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1.kt @@ -0,0 +1,39 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + + +/** + * 两数之和 + * + * 暴力方法时间复杂度是O(n2) + * + * 也可以通过hash表降低查找时间,使其变成O(n),使用hash表注意给定数组中不能出现重复元素 + */ + +fun main() { + val nums = arrayOf(1, 2, 3, 4, 5, 6) + val target = 9 + val resultArray = twoSum(nums, target) + println("结果:${Arrays.toString(resultArray)}") +} + +/** + * 这种方法找不到所有的元素,只能找到一个,当存在里面有重复元素或不止一种情况 + */ +fun twoSum(nums: Array, target: Int): IntArray { + // 一个hashMap存一个value 和position + val map = mutableMapOf() + for (i in 0 until nums.size) { + val targetData = target - nums[i] + if (map.containsKey(targetData)) { + val resultArray = IntArray(2) + resultArray[0] = map[targetData]!! + resultArray[1] = i + return resultArray + } + map.put(nums[i], i) + } + throw IllegalArgumentException("No two sum solution") +} + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1038.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1038.kt new file mode 100644 index 0000000..52c09ab --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1038.kt @@ -0,0 +1,100 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + * 二叉查找树(英语:Binary Search Tree),也称为 二叉搜索树、有序二叉树(Ordered Binary Tree)或排序二叉树(Sorted Binary Tree),是指一棵空树或者具有下列性质的二叉树: + +若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值; +若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值; +任意节点的左、右子树也分别为二叉查找树; +没有键值相等的节点。 +二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低。为 O(\log n)O(logn)。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、多重集、关联数组等。 + +二叉查找树的查找过程和次优二叉树类似,通常采取二叉链表作为二叉查找树的存储结构。中序遍历二叉查找树可得到一个关键字的有序序列,一个无序序列可以通过构造一棵二叉查找树变成一个有序序列,构造树的过程即为对无序序列进行查找的过程。每次插入的新的结点都是二叉查找树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索、插入、删除的复杂度等于树高,期望 O(\log n)O(logn),最坏 O(n)O(n)(数列有序,树退化成线性表)。 + + + +虽然二叉查找树的最坏效率是 O(n)O(n),但它支持动态查询,且有很多改进版的二叉查找树可以使树高为 O(\log n)O(logn),从而将最坏效率降至 O(\log n)O(logn),如 AVL 树、红黑树等。 + + */ +fun main() { + + //右左根的遍历方式 + //记忆搜索 + + //不存在相等的元素 + val arrayNode = arrayOf(4, 1, 6, 0, 2, 5, 7, null, null, null, 3, null, null, null, 8) +// val arrayNode = arrayOf( 6, 5, 7, 8) + var head: TreeNode? = null + arrayNode.forEach { + if (it != null) { + if (head == null) { + head = TreeNode(it) + } else { + createBinearySearchTree(head!!, it!!) + } + } + } + //打出中序遍历结果判断访问是否正确 + head?.let { printNode(it) } + + println() + head?.let { modifyNode(it) } + println() + + + println() + head?.let { printNode(it) } + +} + +fun createBinearySearchTree(head: TreeNode, it: Int) { + + var next: TreeNode? = null + if (it > head.`val`) { + next = head.right + if (next == null) { + head.right = TreeNode(it) + return + } + } else { + next = head.left + if (next == null) { + head.left = TreeNode(it) + return + } + } + + createBinearySearchTree(next, it) + +} + +//中序遍历左跟右 +fun printNode(head: TreeNode) { + head.left?.let { printNode(it) } + print(" ${head.`val`} ") + head.right?.let { printNode(it) } +} + + + +var cacheTotal = 0 +//右 根 左 把每个节点和队列之前的计算和添加到队列, +fun modifyNode(root: TreeNode) { + root.right?.let { modifyNode(it) } + print(" ${root.`val`} ") + if (cacheTotal==0) { + cacheTotal = root.`val` + + } else { + cacheTotal += root.`val` + root.`val` = cacheTotal + } + root.left?.let { modifyNode(it) } +} + +class TreeNode(var `val`: Int) { + var left: TreeNode? = null + var right: TreeNode? = null +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1203.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1203.java new file mode 100644 index 0000000..8954932 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1203.java @@ -0,0 +1,246 @@ +package com.wangpos.datastructure.leetcode; + +import android.support.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * + * 项目之间的依赖关系,用拓扑排序解决。这比较明显。 + * + * 难点在于怎么理解“ 同一小组的项目,排序后在列表中彼此相邻 ”。 + * + * + * 这个题通过提示可以知道使用两层拓扑排序,但是坑还是挺多的。 + * 1.需要给单独的项目创建一个组 + * 2.需要通过项目ID找到关联的组,同时需要维护组本身的序列 + * 一开始想图省事,用一个group[]表示,后来发现这样有耦合问题。改用List维护组本身,用数组维护项目到组的映射关系,初始化List为m个,如果遇到单独的项目, + * 则把当前List大小设置为组ID分配给这个项目。通过这个解决了项目ID和组ID耦合的问题。 + * + */ +public class LeetCode1203 { + + public static void main(String args[]) { + int[] group = new int[]{-1, -1, 1, 0, 0, 1, 0, -1}; + int n = 8; + int m = 2; + List> beforeItems = new ArrayList<>(); + //[[],[6],[5],[6],[3,6],[],[],[]] + beforeItems.add(createItem()); + beforeItems.add(createItem()); + beforeItems.get(beforeItems.size() - 1).add(6); + beforeItems.add(createItem()); + beforeItems.get(beforeItems.size() - 1).add(5); + beforeItems.add(createItem()); + beforeItems.get(beforeItems.size() - 1).add(6); + beforeItems.add(createItem()); + beforeItems.get(beforeItems.size() - 1).add(3); + beforeItems.get(beforeItems.size() - 1).add(6); + beforeItems.add(createItem()); + beforeItems.add(createItem()); + beforeItems.add(createItem()); + + int[] result = new LeetCode1203().sortItems(8, 2, group, beforeItems); + System.out.println(Arrays.toString(result)); + + } + + @NonNull + private static List createItem() { + return new ArrayList(); + } + + + Listqueue = new LinkedList<>(); + + + //项目 + static class Item { + //项目id + int id; + + //初始化入度 + int inputCnt; + //下一个项目 + List nextItems = new ArrayList<>(); + + Item(int id) { + this.id = id; + } + } + + //组 + static class Group { + //组id + int id; + + //入度 + int inputCnt; + + List items = new ArrayList<>(); + //下一个 组 + List nextGroups = new ArrayList<>(); + + Group(int id) { + this.id = id; + } + } + + /** + * 使用邻接表的形式 + * @param n + * @param m + * @param group + * @param beforeItems + * @return + */ + public int[] sortItems(int n, int m, int[] group, List> beforeItems) { + //项目数组 + Item[] items = new Item[n]; + + //用来保存已经绑定过item的group数组 + Group[] itemToGroup = new Group[n]; + + //组 + List oriGroups = new ArrayList<>(); + + //初始化组种类 + for (int j = 0; j < m; j++) { + oriGroups.add(new Group(j)); + } + + //初始化项目 + for (int i = 0; i < n; i++) { + items[i] = new Item(i); + } + + /** + * 遍历每个项目,所属组 + */ + for (int i = 0; i < group.length; i++) { + int groupId = group[i]; + if (groupId == -1) {// 项目不属于任何组 + //创建一个新组 + Group temp = new Group(oriGroups.size()); + //保存到组列表 + oriGroups.add(temp); + //组绑定这个项目,因为项目是按顺序的所以i就是这个项目 + temp.items.add(i); + itemToGroup[i] = temp; + } else { + //根据组id 绑定项目 + oriGroups.get(groupId).items.add(i); + itemToGroup[i] = oriGroups.get(groupId); + } + } + + for (int i = 0; i < beforeItems.size(); i++) { + List array = beforeItems.get(i); + //初始化入度 + items[i].inputCnt = array.size(); + for (Integer itemId : array) { + //每个项目的下一个项目 + items[itemId].nextItems.add(i); + //获取绑定项目后的组 + Group beforeGroup = itemToGroup[itemId]; + //当前组 + Group curGroup = itemToGroup[i]; + if (beforeGroup != curGroup) { + //前一个组保存他的下一个组 + beforeGroup.nextGroups.add(curGroup); + //当前组入度多1 + curGroup.inputCnt++; + } + } + } + + Queue groupQueue = new LinkedList<>(); + + //找到入度为0的组添加到待遍历的队列中 + for (Group ele : oriGroups) { + if (ele.inputCnt == 0) { + groupQueue.offer(ele); + } + } + + if (groupQueue.isEmpty()) { + return new int[0]; + } + + int[] result = new int[n]; + int resultIndex = 0; + while (!groupQueue.isEmpty()) { + int size = groupQueue.size(); + for (int i = 0; i < size; i++) { + Group curGroup = groupQueue.poll(); + Queue itemQueue = new LinkedList<>(); + if (curGroup.items.isEmpty()) { + continue; + } + + //再进行item拓扑排序 + for (int temp : curGroup.items) { + if (items[temp].inputCnt == 0) { + itemQueue.offer(temp); + } + } + + if (itemQueue.isEmpty()) { + return new int[0]; + } + + // + while (!itemQueue.isEmpty()) { + int itemQueueSize = itemQueue.size(); + for (int j = 0; j < itemQueueSize; j++) { + Integer itemId = itemQueue.poll(); + //保存结果 + result[resultIndex++] = itemId; + //遍历下一个 + for (int nextItemId : items[itemId].nextItems) { + items[nextItemId].inputCnt--; + if (items[nextItemId].inputCnt == 0 && curGroup.items.contains(nextItemId)) { + itemQueue.offer(nextItemId); + } + } + } + } + + //项目中存在环 + for (int itemId : curGroup.items) { + if (items[itemId].inputCnt > 0) { + return new int[0]; + } + } + + //遍历下一个组 + for (Group nextGroup : curGroup.nextGroups) { + nextGroup.inputCnt--; + if (nextGroup.inputCnt == 0) { + groupQueue.offer(nextGroup); + } + } + } + } + //组中存在环 + for (Group ele : oriGroups) { + if (ele.inputCnt > 0) { + return new int[0]; + } + } + + for (int k = 0; k < items.length; k++) { + if (items[k].inputCnt > 0) { + return new int[0]; + } + } + + return result; + } + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1214.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1214.java new file mode 100644 index 0000000..65e3b74 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1214.java @@ -0,0 +1,101 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.Stack; + +public class LeetCode1214 { + + public boolean twoSumBSTs(TreeNode root1, TreeNode root2, int target) { + + Stack stack1 = new Stack<>(); + Stack stack2 = new Stack<>(); + searchRoot11(root1, stack1); + searchRoot22(root2, stack2); + int t = 0; + int small = stack1.pop(); + int large = stack2.pop(); + while (!stack1.empty() || !stack2.empty()) { + t = small + large; + if (t == target) { + return true; + } else if (t > target) { + // 取较小的 + if(!stack1.empty()) { + small = stack1.pop(); + }else{ + break; + } + } else { + // 取较大的 + if(!stack2.empty()) { + large = stack2.pop(); + }else{ + break; + } + } + } + return false; + } + + private void searchRoot11(TreeNode root1, Stack stack) { + if (root1 == null) { + return; + } + searchRoot11(root1.getLeft(), stack); + stack.add(root1.getVal()); + searchRoot11(root1.getRight(), stack); + } + + private void searchRoot22(TreeNode root1, Stack stack) { + + if (root1 == null) { + return; + } + searchRoot22(root1.getRight(), stack); + stack.add(root1.getVal()); + searchRoot22(root1.getLeft(), stack); + } +// +// fun twoSumBSTs2(root1: TreeNode?, root2: TreeNode?, target: Int): Boolean { +// +// val stack1 = Stack()//从小到大 top最大 +// val stack2 = Stack()//从大到小 +// searchRoot11(root1, stack1) +// searchRoot22(root2, stack2) +// var t = 0 +// var small = stack1.pop() +// var large = stack2.pop() +// while (stack1.isNotEmpty() && stack2.isNotEmpty()) { +// t = small + large +// println(small) +// println(large) +// if (t == target) { +// return true +// } else if (t > target) { +// // 取较小的 +// small = stack1.pop() +// } else { +// // 取较大的 +// large = stack2.pop() +// } +// } +// return false +// } +// +// fun searchRoot11(root1: TreeNode?, stack: Stack) { +// if (root1 == null) { +// return +// } +// searchRoot11(root1.left, stack) +// stack.add(root1.`val`) +// searchRoot11(root1.right, stack) +// } +// +// fun searchRoot22(root2: TreeNode?, stack: Stack) { +// if (root2 == null) { +// return +// } +// searchRoot22(root2.right, stack) +// stack.add(root2.`val`) +// searchRoot22(root2.left, stack) +// } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1214.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1214.kt new file mode 100644 index 0000000..8cbc305 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1214.kt @@ -0,0 +1,138 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +fun main() { + + val root1Array = arrayOf(0, -10, 10) + + val root2Array = arrayOf(5, 1, 7, 0, 2) + + val target = 18 + + var root1: TreeNode? = null + var root2: TreeNode? = null + root1Array.forEach { + if (it != null) { + if (root1 == null) { + root1 = TreeNode(it) + } else { + createBinearySearchTree(root1!!, it!!) + } + } + } + + root2Array.forEach { + if (it != null) { + if (root2 == null) { + root2 = TreeNode(it) + } else { + createBinearySearchTree(root2!!, it!!) + } + } + } + +// root1?.let { printNode(it) } +// println() +// root2?.let { printNode(it) } + +// val result = twoSumBSTs(root1, root2, target) + +// val result = twoSumBSTs2(root1, root2, target) + val result = LeetCode1214().twoSumBSTs(root1,root2,target); + println("结果:${result}") +} + +/** + * 这种双层嵌套效率比较低,中序遍历时间复杂度是O(n) 后面的查找属于也是中序遍历所以O(n*n) + * + */ +fun twoSumBSTs(root1: TreeNode?, root2: TreeNode?, target: Int): Boolean { + searchRoot1(root1, root2, target) + return searchResult +} + +fun searchRoot1(root1: TreeNode?, root2: TreeNode?, target: Int) { + + if (root1 == null || searchResult) { + return + } + searchRoot1(root1?.left, root2, target) + if (root1.`val` > target) { + return + } + searchRoot2(root2, target - root1.`val`) + searchRoot1(root1?.right, root2, target) +} + +var searchResult = false +fun searchRoot2(root2: TreeNode?, i: Int) { + if (root2 == null || searchResult) { + return + } + searchRoot2(root2.left, i) + if (root2.`val` == i) { + searchResult = true + return + } + if (root2.`val` > i) { + return + } + searchRoot2(root2.right, i) +} + +/** + * 将两个树进行中序遍历,和逆向中序,时间复杂度 O(n)*2 + * 然后放入两个栈中,得到两个一个从小到大,一个从大到小的 + * + * 然后遍历两个栈,如果相加之和小于t,就从最小的栈去继续找,反之从大的栈中找 + * + * + */ +fun twoSumBSTs2(root1: TreeNode?, root2: TreeNode?, target: Int): Boolean { + + val stack1 = Stack()//从小到大 top最大 + val stack2 = Stack()//从大到小 + searchRoot11(root1, stack1) + searchRoot22(root2, stack2) + var t = 0 + var small = stack1.pop() + var large = stack2.pop() + while (stack1.isNotEmpty() || stack2.isNotEmpty()) { + t = small + large + println(small) + println(large) + if (t == target) { + return true + } else if (t > target) { + // 取较小的 + if(stack1.isNotEmpty()) { + small = stack1.pop() + } + } else { + // 取较大的 + if(stack1.isNotEmpty()) { + large = stack2.pop() + } + } + } + return false +} + +fun searchRoot11(root1: TreeNode?, stack: Stack) { + if (root1 == null) { + return + } + searchRoot11(root1.left, stack) + stack.add(root1.`val`) + searchRoot11(root1.right, stack) +} + +fun searchRoot22(root2: TreeNode?, stack: Stack) { + if (root2 == null) { + return + } + searchRoot22(root2.right, stack) + stack.add(root2.`val`) + searchRoot22(root2.left, stack) +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1272.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1272.java new file mode 100644 index 0000000..cf64245 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode1272.java @@ -0,0 +1,139 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * 给你一个 有序的 不相交区间列表 intervals 和一个要删除的区间 toBeRemoved, intervals 中的每一个区间 intervals[i] = [a, b] 都表示满足 a <= x < b 的所有实数  x 的集合。 + * + * 我们将 intervals 中任意区间与 toBeRemoved 有交集的部分都删除。 + * + * 返回删除所有交集区间后, intervals 剩余部分的 有序 列表。 + * + * + * 示例 1: + * + * 输入:intervals = [[0,2],[3,4],[5,7]], toBeRemoved = [1,6] + * 输出:[[0,1],[6,7]] + * 示例 2: + * + * 输入:intervals = [[0,5]], toBeRemoved = [2,3] + * 输出:[[0,2],[3,5]] + * + */ +public class LeetCode1272 { + + public static void main(String[] args) { + LeetCode1272 testObject = new LeetCode1272(); +// int[][] nums = new int[][]{new int[]{3, 4}, new int[]{0, 3}, new int[]{0, 2}, new int[]{5, 7}}; +// int[] toBeRemoved = new int[]{1, 6}; + +// int[][] nums = new int[][]{new int[]{0, 5}}; +// int[] toBeRemoved = new int[]{2, 3}; + +// int[][] nums = new int[][]{new int[]{-5, -4}, new int[]{-3, -2}, new int[]{1, 2}, new int[]{3, 5},new int[]{8,9}}; +// int[] toBeRemoved = new int[]{-1, 4}; + + int[][] nums = new int[][]{new int[]{0, 100}}; + int[] toBeRemoved = new int[]{0, 50}; + testObject.removeInterval(nums, toBeRemoved); + } + + public List> removeInterval(int[][] intervals, int[] toBeRemoved) { + + //排序分别比较两个条件,按顺序比较 + sortArray(intervals, new int[]{0, 1}); + + /** + * + * 0 2 0 3 3 4 5 7 + * 1 6 + * + * 0 1 6 7 + * + */ + + + List> result = new ArrayList<>(); + int[] lastSaveArray = null; + for (int[] interval : intervals) { + if (interval[0] <= toBeRemoved[0]) { + if (interval[1] > toBeRemoved[0]) { + //修改 入队列 + + int backupDataRight = interval[1]; + if (interval[0] != toBeRemoved[0]) { + interval[1] = toBeRemoved[0]; + + if (lastSaveArray == null) { + lastSaveArray = saveResult(result, interval); + } else if (lastSaveArray[0] == interval[0] && lastSaveArray[1] == interval[1]) { + continue; + } else { + lastSaveArray = saveResult(result, interval); + } + + } + + if (backupDataRight >= toBeRemoved[1]) { + int newArray[] = new int[]{toBeRemoved[1], backupDataRight}; + lastSaveArray = saveResult(result, newArray); + } + + } else { + lastSaveArray = saveResult(result, interval); + } + } else { + // 8 9 -1 4 + // + if (interval[1] <= toBeRemoved[1]) { + // 8 9 1 10 + //重复空间 +// lastSaveArray = saveResult(result, interval); + } else { + if (interval[0] <= toBeRemoved[1]) { + interval[0] = toBeRemoved[1]; + } + lastSaveArray = saveResult(result, interval); + } + } + } + + + return result; + } + + private int[] saveResult(List> result, int[] interval) { + int[] lastSaveArray; + lastSaveArray = interval; + List cell = new ArrayList(); + cell.add(interval[0]); + cell.add(interval[1]); + result.add(cell); + return lastSaveArray; + } + + private void sortArray(int[][] intervals, final int[] order) { + Arrays.sort(intervals, new Comparator() { + @Override + public int compare(Object o1, Object o2) { + int[] one = (int[]) o1; + int[] two = (int[]) o2; + + for (int i = 0; i < order.length; i++) { + int k = order[i]; + if (one[k] > two[k]) { + return 1; + } else if (one[k] < two[k]) { + return -1; + } else { + continue; //如果按一条件比较结果相等,就使用第二个条件进行比较。 + } + } + return 0; + } + }); + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode2.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode2.kt new file mode 100644 index 0000000..094e756 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode2.kt @@ -0,0 +1,76 @@ +package com.wangpos.datastructure.leetcode + +/** + * + * + */ +fun main() { + + val l1 = ListNode(2) + l1?.next = ListNode(4) + l1?.next?.next = ListNode(3) + + val l2 = ListNode(5) + l2.next = ListNode(6) + l2?.next?.next = ListNode(4) + + val result = addTwoNumbers(l1, l2) + + if (result != null) { + printNode(result) + } + + +} + +fun printNode(node: ListNode) { + if (node != null) { + print(node.`val`) + } + node.next?.let { printNode(it) } +} + +class ListNode(var `val`: Int) { + var next: ListNode? = null +} + +fun addTwoNumbers(l1: ListNode?, l2: ListNode?): ListNode? { + var currentL1Node = l1 + var currentL2Node = l2 + var result: ListNode? = null + var currentResult: ListNode? = result + //进位存储 + var tempResult = 0 + //终止条件,其中有一个不为null就可以循环 + while (currentL1Node != null || currentL2Node != null) { + var resultValue = 0 + if (tempResult != 0) { + resultValue += tempResult + tempResult = 0 + } + if (currentL1Node != null) { + resultValue += currentL1Node.`val` + } + if (currentL2Node != null) { + resultValue += currentL2Node.`val` + } + if (resultValue > 9) { + resultValue -= 10 + tempResult = 1 + } + val newNode = ListNode(resultValue) + if (currentResult == null) { + currentResult = newNode + result = currentResult + } else { + currentResult.next = newNode + } + currentResult = newNode + currentL1Node = currentL1Node?.next + currentL2Node = currentL2Node?.next + } + if (tempResult != 0) { + currentResult?.next = ListNode(1) + } + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode20.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode20.kt new file mode 100644 index 0000000..4637904 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode20.kt @@ -0,0 +1,57 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + *栈的最大特点就是后进先出(LIFO)。对于栈中的数据来说,所有操作都是在栈的顶部完成的,只可以查看栈顶部的元素,只能够向栈的顶部压⼊数据,也只能从栈的顶部弹出数据。 + * + * 应用场景:在解决某个问题的时候,只要求关心最近一次的操作,并且在操作完成了之后,需要向前查找到更前一次的操作。 + * + * 第 20 题:给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 + * + */ +fun main() { + + val rightStr = "{[()]}" + val errorStr = "{}{{))" + var result = isValid(rightStr) + + println("result = ${result}") +} + +fun isValid(data: String): Boolean { + + var charArray = data.toCharArray() + val stack = Stack() + charArray.forEach { + if (stack.empty()) { + stack.push(it) + } else { + if (checkRight(stack, it)) {//it-1表示是一对的 + stack.pop() + } else { + stack.push(it) + } + } + } + + if (stack.isEmpty()) { + return true + } + return false +} + +fun checkRight(stack: Stack, it: Char): Boolean { + + if (stack.peek() == '{' && it == '}') { + return true + } + if (stack.peek() == '[' && it == ']') { + return true + } + if (stack.peek() == '(' && it == ')') { + return true + } + return false + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode207.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode207.java new file mode 100644 index 0000000..f13a2b2 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode207.java @@ -0,0 +1,115 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.LinkedList; + +/** + * + * 广度优先搜索 通过保存一个入度表 + * + * 然后取出入度为0的元素添加到队列 + * + * 遍历入度为0的队列,如果到下一个有边,将下一个入度--1,当所有下一个边都--1 相当于移除这个点 + * + * 如果下一个点的入度为0 就添加到遍历的队列 + * + * 在遍历的时候计数一下看看是否所有点都遍历到可以判断环形 + * + * + * 深度优先搜索, + * + * 通过flag数组标识 0 未遍历 1遍历中,-1遍历完成 + * + * 如果遍历下一个点的时候判断1标识存在环 + * + * 遍历下一个节点,然后再递归调用 + * + * 邻接矩阵通过 graph[][]==1判断有没有边 + * 通过遍历依赖关系列表 [0]位置出现的次数就是入度,比如[1,3]证明 3到1的边,1的入度为1 + * + * 邻接表,可以通过数组中集合的个数表示入度 + * + * + */ +public class LeetCode207 { + + /** + * 时间复杂度 O(N + M)O(N+M),遍历一个图需要访问所有节点和所有临边,NN 和 MM 分别为节点数量和临边数量; + * 空间复杂度 O(N)O(N),为建立邻接矩阵所需额外空间。 + * + * @param numCourses + * @param prerequisites + * @return + */ + public boolean canFinish(int numCourses, int[][] prerequisites) { + + //统计课程安排图中每个节点的入度,生成 入度表 indegrees + int[] indegrees = new int[numCourses]; + + //我们定义的二维数组中,每个数组的含义是前一个依赖后一个, + //相当于先学完数组[1]号位元素才能学习数组[0] + //所以按照有向图的构成是数组[1]指向数组[0]的一条边,所以数组[0]的入度为1(相当于有一个箭头指向) + //由前面条件可以知道,遍历数组,统计每一个数组[0]号位置的出现次数就是他的入度数 + for (int[] cp : prerequisites) indegrees[cp[0]]++; + //保存入度为0的元素,也就是拓扑排序出发点 + LinkedList queue = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (indegrees[i] == 0) queue.addLast(i); + } + //从每个出发点出发开始遍历 + while (!queue.isEmpty()) { + //取出入度为0的元素 + Integer pre = queue.removeFirst(); + //每次遍历,相当于从这个起点出发的所有边都遍历到了,所以相当于这个点遍历完就完成了一个课程,这就是广度优先搜索 + numCourses--; + //遍历其余边 + for (int[] req : prerequisites) { + //广度优先搜索就是逐个遍历,任意找到一个含有当前起点的边,如果不包含就跳过 + if (req[1] != pre) continue; + //找到之后,将这条边去掉,也就是将所指方向的点的入度减一 + //如果其入度也变成0了就加入到起点队列,方便下次遍历后也包含此点 + //如果不存在环则可以按照某个顺序遍历所有点 + if (--indegrees[req[0]] == 0) queue.add(req[0]); + } + } + return numCourses == 0; + } + + + public boolean canFinish2(int numCourses, int[][] prerequisites) { + int[][] adjacency = new int[numCourses][numCourses]; + //定义节点标识 + int[] flags = new int[numCourses]; + //将数组变成矩阵 + for (int[] cp : prerequisites) + adjacency[cp[1]][cp[0]] = 1; + for (int i = 0; i < numCourses; i++) { + if (!dfs(adjacency, flags, i)) return false; + } + return true; + } + + /** + * 这里使用了一个小技巧,就是通过标识 0未被访问,1 标识正在被这条分支访问中,-1标识这个分支访问结束 + * 所以可以很容易判断 如果被访问状态是1状态就说明存在了环路 + * + * @param adjacency + * @param flags + * @param i + * @return + */ + private boolean dfs(int[][] adjacency, int[] flags, int i) { + if (flags[i] == 1) return false;//表示本次访问的节点还没访问结束又被子节点访问了,所以就存在了环路 + if (flags[i] == -1) return true;//表示此点已被访问 + if (flags[i] == 0) flags[i] = 1; //当前已被访问,继续这个点的子节点 + for (int j = 0; j < adjacency.length; j++) { + //adjacency[i][j] == 1 表示之间有边 + if (adjacency[i][j] == 1 && !dfs(adjacency, flags, j)) return false; + + } + ///当前已被访问完毕,子节点也访问完毕 + flags[i] = -1; + return true; + } + +} + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode207.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode207.kt new file mode 100644 index 0000000..f105267 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode207.kt @@ -0,0 +1,22 @@ +package com.wangpos.datastructure.leetcode + +/** + * 207. 课程表 + * + * 现在你总共有 n 门课需要选,记为 0 到 n-1。 + +在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1] + +给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习? + + */ +fun main() { + + val data = arrayOf(arrayOf(1,0).toIntArray(),arrayOf(0,1).toIntArray()) + + //统计课程安排图中每个节点的入度,生成 入度表 indegrees + + val result = LeetCode207().canFinish(2,data) + + println("结果:${result}") +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode210.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode210.java new file mode 100644 index 0000000..ed8afae --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode210.java @@ -0,0 +1,91 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.LinkedList; +import java.util.Stack; + +public class LeetCode210 { + + public int[] findOrder(int numCourses, int[][] prerequisites) { + + Stack stack = new Stack(); + int[] array = new int[0]; + if (canFinish2(numCourses, prerequisites, stack)) { + array = new int[numCourses]; + } else { + return array; + } + + int index = 0; + while (!stack.isEmpty()) { + array[index] = stack.pop(); + index++; + } + return array; + } + + /** + * 时间复杂度: O(N),其中NN为课程数。我们需要要对森林中的所有结点执行完全的深度优先搜索。之所以是森林而不是图,是因为并非所有结点都连接在一起。也可能存在不连通的部分。 + * 空间复杂度: O(N), 递归栈占用的空间(不是用于存储拓扑排序的栈)。 + * 利用深度优先搜索 + */ + + public boolean canFinish2(int numCourses, int[][] prerequisites, Stack stack) { + int[][] adjacency = new int[numCourses][numCourses]; + //定义节点标识 + int[] flags = new int[numCourses]; + //将数组依赖关系变成矩阵 + for (int[] cp : prerequisites) + adjacency[cp[1]][cp[0]] = 1; + + for (int i = 0; i < numCourses; i++) { + if (!dfs(numCourses, adjacency, flags, i, stack)) { + return false; + } + } + return true; + } + + /** + * 这里使用了一个小技巧,就是通过标识 0未被访问,1 标识正在被这条分支访问中,-1标识这个分支访问结束 + * 所以可以很容易判断 如果被访问状态是1状态就说明存在了环路 + * + * @param adjacency + * @param flags + * @param i + * @return + */ + private boolean dfs(int numCourses, int[][] adjacency, int[] flags, int i, Stack stack) { + if (flags[i] == 1) return false;//表示本次访问的节点还没访问结束又被子节点访问了,所以就存在了环路 + if (flags[i] == -1) { + return true;//表示此点已被访问 + } + if (flags[i] == 0) flags[i] = 1; //当前已被访问,继续这个点的子节点 + for (int j = 0; j < adjacency.length; j++) { + //adjacency[i][j] == 1 判断有边,如果result false证明有环就return + if (adjacency[i][j] == 1 && !dfs(numCourses, adjacency, flags, j, stack)) return false; + + //等价代码 +// if (adjacency[i][j] == 1) { +// boolean result = dfs(numCourses, adjacency, flags, j, stack); +// if (!result) { +// //有环路 +// return false; +// } +// } + + } + ///当前已被访问完毕,子节点也访问完毕 + + flags[i] = -1; + //保存遍历 + stack.push(i); + return true; + } + + + /** + * 方法二: 利用结点的入度 + */ + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode210.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode210.kt new file mode 100644 index 0000000..7d9a6f0 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode210.kt @@ -0,0 +1,37 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + * 现在你总共有 n 门课需要选,记为 0 到 n-1。 + +在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1] + +给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。 + +可能会有多个正确的顺序,你只要返回一种就可以了。如果不可能完成所有课程,返回一个空数组 + */ + +fun main() { + + val course = arrayOf( + arrayOf(1, 0).toIntArray(), + arrayOf(2, 0).toIntArray(), + arrayOf(3, 1).toIntArray(), + arrayOf(3, 2).toIntArray() + ) + + val num = course.size + + //深度优先搜索,判断无环就可以完全修,并且保存所有路径 + + //将课程数组转化成邻接矩阵 + + // + + val result = LeetCode210().findOrder(4,course) + + println("result=${Arrays.toString(result)}") + + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode230.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode230.kt new file mode 100644 index 0000000..83f3a8e --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode230.kt @@ -0,0 +1,120 @@ +package com.wangpos.datastructure.leetcode + +/** +LeetCode 第 230 题 + +给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 + +说明:你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。 + + +解题思路 + +这道题考察了两个知识点: +二叉搜索树的性质 +二叉搜索树的遍历 + +二叉搜索树的性质:对于每个节点来说,该节点的值比左孩子大,比右孩子小,而且一般来说,二叉搜索树里不出现重复的值。 + +二叉搜索树的中序遍历是高频考察点,节点被遍历到的顺序是按照节点数值大小的顺序排列好的。即,中序遍历当中遇到的元素都是按照从小到大的顺序出现。 + +因此,我们只需要对这棵树进行中序遍历的操作,当访问到第 k 个元素的时候返回结果就好。 + */ +fun main() { + + val array = arrayOf(5, 3, 6, 2, 4, 1) + val tree = buildTree(array) + + // [1, 2, 3, 4, 5, 6] + // 3 + println(kthSmallest(tree, 3)) + +} + + +fun kthSmallest(tree: Tree, k: Int): Int { + val resultList = arrayListOf() + findTree(tree, k, resultList) + + println(resultList) + return resultList[k - 1] +} + +//利用中序遍历 +fun findTree(root: Tree, k: Int, result: ArrayList) { +// if (result.size == k) {//找到第k就不要找了 +// return +// } + root.left?.let { findTree(it, k, result) } + result.add(root.data) + root.right?.let { findTree(it, k, result) } +} + + +fun buildTree(array: Array): Tree { + var root: Tree? = null + array.forEach { + if (root == null) { + root = Tree(data = 5) + } else { + addTree(root!!, it) + } + } +// +// root?.let { printTree_ROOT_LEFT_RIGHT(it) } +// println() +// root?.let { printTree_LEFT_ROOT_RIGHT(it) } +// println() +// root?.let { printTree_LEFT_RIGHT__ROOT(it) } + + return root!! +} + +//先序遍历 +fun printTree_ROOT_LEFT_RIGHT(root: Tree) { + print(root.data) + root.left?.let { printTree_ROOT_LEFT_RIGHT(it) } + root.right?.let { printTree_ROOT_LEFT_RIGHT(it) } +} + +//中序遍历 +fun printTree_LEFT_ROOT_RIGHT(root: Tree) { + root.left?.let { printTree_LEFT_ROOT_RIGHT(it) } + print(root.data) + root.right?.let { printTree_LEFT_ROOT_RIGHT(it) } +} + +//后序遍历 +fun printTree_LEFT_RIGHT__ROOT(root: Tree) { + root.left?.let { printTree_LEFT_RIGHT__ROOT(it) } + root.right?.let { printTree_LEFT_RIGHT__ROOT(it) } + print(root.data) +} + +fun addTree(root: Tree, it: Int) { + + if (it > root.data) { + addRight(root, it) + } else { + addLeft(root, it) + } +} + +fun addLeft(root: Tree, it: Int) { + if (root.left != null) { + addTree(root.left!!, it) + } else { + root.left = Tree(data = it) + } + +} + +private fun addRight(root: Tree, it: Int) { + if (root.right != null) { + addTree(root.right!!, it) + } else { + root.right = Tree(data = it) + } +} + +class Tree(var left: Tree? = null, var data: Int, var right: Tree? = null) \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode239.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode239.kt new file mode 100644 index 0000000..0ce81aa --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode239.kt @@ -0,0 +1,85 @@ +package com.wangpos.datastructure.leetcode + +import com.wangpos.datastructure.algorithm.search +import java.util.* +import kotlin.collections.ArrayList + + +/** + * + * + * LeetCode 第 239 题:给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字,滑动窗口每次只向右移动一位。返回滑动窗口最大值。 + +注意:你可以假设 k 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。 + +示例:给定一个数组以及一个窗口的长度 k,现在移动这个窗口,要求打印出一个数组,数组里的每个元素是当前窗口当中最大的那个数。 +输入:nums = [1, 3, -1, -3, 5, 3, 6, 7],k = 3 +输出:[3, 3, 5, 5, 6, 7] + */ + +fun main() { + + val nums = intArrayOf(1, 3, -1, -3, 5, 3, 6, 7) + + val k = 3//k = 8代表总长度 + val result = search(nums, k) + println(result.toString()) + +} + +/** + * 排序算法通常容易犯错 // val temp = nums[i] 一次访问赋值,记住这里不是指针,Java中没有指针,想要动态获取值,只能通过游标 + */ +fun search(nums: IntArray, k: Int): ArrayList { + val dQueue = LinkedList() + val resultList = arrayListOf() + //先将k排序,大到小 + + for (i in 0..(k - 2)) { //kotlin 遍历包含右边值,所以要减掉1,因为直接插入排序,少比较一个所以再减1 + // val temp = nums[i]//一次访问赋值,记住这里不是指针,Java中没有指针,想要动态获取值,只能通过游标 + for (j in (i + 1)..(k - 1)) { + if (nums[j] > nums[i]) { + val temp = nums[i] + nums[i] = nums[j] + nums[j] = temp + } + } + } + + // 将k个放入到队列滑块中,这里可以保证第一个是最大的 + + for (i in 0..(k - 1)) { + dQueue.add(nums[i]) + } + resultList.add(dQueue.first) + + for (j in k..(nums.size - 1)) { + var data = nums[j] + //是否加入队列 + ifAddQueue(dQueue, data) + //记录最大值,取队头就可以 + resultList.add(dQueue.first) + } + return resultList + +} + +private fun ifAddQueue(dQueue: LinkedList, data: Int) { + while (!dQueue.isEmpty()) { + val minData = dQueue.peekLast() + if (data > minData) { + dQueue.removeLast() + } else { + // 非空 且小于3 入队列 + if (dQueue.size < 3) { + dQueue.addLast(data) + } + //记住退出,队列没有比他再小的了退出 + break + } + } + //都被移除了,ok 你最大进来吧 + if (dQueue.isEmpty()) { + dQueue.add(data) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode242.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode242.kt new file mode 100644 index 0000000..bb921c8 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode242.kt @@ -0,0 +1,37 @@ +package com.wangpos.datastructure.leetcode + +/** + * 给定一个字符串 s 和 t + * + * 判断t 是否是s的异位词 + * + * 假设字母都是小写的 + * + * 异位词 就是相同的字符数量 + */ +fun main() { + + val result = isEctopicWords("ahelloworld", "aworldhello") + println(result) +} + +fun isEctopicWords(t: String, s: String): Boolean { + var arrays = IntArray(26) + val tArray = t.toCharArray() + val sArray = s.toCharArray() + if (t.length != s.length) { + return false + } + + for (i in 0 until t.length) { + arrays[tArray[i] - 'a'] += 1 + arrays[sArray[i] - 'a'] -= 1 + } + + arrays.forEach { + if (it != 0) { + return false + } + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode25.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode25.kt new file mode 100644 index 0000000..e9673aa --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode25.kt @@ -0,0 +1,80 @@ +package com.wangpos.datastructure.leetcode + + +/** + * + * LeetCode 第 25 题:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。 + * + * k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 + * + * 说明: +你的算法只能使用常数的额外空间。 +你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 + +给定这个链表:1-›2-›3-›4-›5 +当 k=2 时,应当返回:2-›1-›4-›3-›5 +当 k=3 时,应当返回:3-›2-›1-›4-›5 + + +解题思路 + +这道题考察了两个知识点: +对链表翻转算法是否熟悉 +对递归算法的理解是否清晰 + */ +fun main() { + var originNode = Node(1, null) + var current = originNode + for (i in 2 until 6) { + val newNode = Node(i) + current.next = newNode + current = newNode + } +// printNode(prev) + val k = 3 + val newNode = reverseGroup(originNode, k) + newNode?.let { printNode(it) } + + +} +fun reverseGroup(head: Node?, k: Int): Node? { + var n = k + var prev: Node? = null + var curr: Node? = head + + if (curr != null) { + //每次反转前检查一下是否满足反转的数量 + var m = n + var checkCurrent = curr + while (checkCurrent != null && m-- > 0) { + checkCurrent = checkCurrent.next + } + if (m > -1) { + return head + } + } + + while (curr != null && n-- > 0) { + val next = curr.next + curr.next = prev + prev = curr + curr = next + } + + head?.next = reverseGroup(curr, k) + //每次返回第一次反转后的指针 + return prev + +} + +fun printNode(prev: Node) { + println(prev.data) + if (prev.next != null) { + printNode(prev.next!!) + } + +} + + +class Node(var data: Int, var next: Node? = null) + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode292.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode292.kt new file mode 100644 index 0000000..313b7d6 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode292.kt @@ -0,0 +1,18 @@ +package com.wangpos.datastructure.leetcode + +/** + * 292. Nim 游戏 + */ +fun main() { + +} + +fun canWinNim(n: Int): Boolean { + //取1 + if(n<=3) return true + + if(n%4==0)return false + + return true + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode3.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode3.kt new file mode 100644 index 0000000..0eaaa35 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode3.kt @@ -0,0 +1,49 @@ +package com.wangpos.datastructure.leetcode + +/** + * 无重复字符的最长子串 + */ +fun main() { + + println(lengthOfLongestSubstring("abcabcdbb")) +} + +fun lengthOfLongestSubstring(s: String): Int { + //滑块方式 可以使用hashMap + val dQueue = mutableMapOf() + //初始化,添加到滑块当中,直到有重复数据 + + //移动滑块,也就是将其他数据分别和滑块中数据比较,如果不包含就加入 + + var resultSize = 0 + + val charArray = s.toCharArray() + var startIndex =0 + for (i in 0 until charArray.size) { + if (dQueue.contains(charArray[i])) { + //移除掉自己和之前的元素 + println("remove ${charArray[i]}") + val position = dQueue[charArray[i]]!! + var index = position + while (index >= startIndex) { + val data = charArray[index] + //判断此元素是之前添加的元素才移除 + if (dQueue.containsKey(data) && dQueue[data] == index) { + println("移除:${data}") + dQueue.remove(data) + } + index-- + } + startIndex = position + } + println(">>>>>>>>>>>${dQueue.size}") + dQueue[charArray[i]] = i + if (dQueue.size > resultSize) { + resultSize = dQueue.size + } + println("add ${resultSize} ${dQueue.size} ${charArray[i]}") + } + + return resultSize + //最后返回size +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode307.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode307.kt new file mode 100644 index 0000000..137f0a4 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode307.kt @@ -0,0 +1,175 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + * 给定一个整数数组  nums,求出数组从索引 i 到 j  (i ≤ j) 范围内元素的总和,包含 i,  j 两点。 + +update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。 + + +频繁计算总和可以用这总结构,同时这种结构更新效率不再是O(1) + */ +fun main() { + + val result = NumArray(arrayOf(1, 3, 5).toIntArray()).sumRange(0,2) + + println("结果:${result}") +} + +class NumArray(nums: IntArray) { + + lateinit var tree: IntArray + + + init { + if (nums.size > 0) { + val n = nums.size + //数组元素不一定全部用满 + tree = IntArray(n * 2) + buildTree(nums) + } + } + + /** + * 先将叶子节点添加到tree 数组中 + * 从2n-1 到 n,因为顺序,所以从n 到2n-1 + * + * 求所有符节点 = 2*i +2*i+1 i从1开始 + * + * 最终数组中0号元素没用,1号元素才是树的根 + * + * 我们是从底部向上逆序,导致多出来一个0号元素,如果是从上到下,就会多出一个2n-1号元素 + * + * 这个二叉树克制,从底向上,从右向左 + * + * 注意:::最终得到的也不是完全二叉树 + * + * 因为完全二叉树定义:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树 + * + * + * + * 9 8 + * + * 1(3) 3(4) 5(5) + * + * 从右向左底部线排列好叶子节点,然后计算父节点 2*i +2*i+1,8 = 3 + 5,当叶子节点不够,父节点也会充当叶子节点去,比如9 = 8+1 + * + * 线段树 + */ + private fun buildTree(nums: IntArray) { + var i = n //恰好第n个位置为叶子节点,不如3个数据,n=3 表示我们需要3个叶子节点,至少需要2*n的数组来存储,构建一个6个节点的树 + var j = 0 + /** + * 相当于数组n 到 2n-1,一共n个数据 + */ + while (i < 2 * n) { + //初始化叶子节点 + println(""+i+" "+j) + + tree[i] = nums[j] +// println(nums[j]) + i++ + j++ + } + + println(Arrays.toString(tree)) + + i = n-1 + //初始化和 + while (i>0){ + println(i) + tree[i] = tree[i * 2] + tree[i * 2 + 1] + i-- + } + + println(Arrays.toString(tree)) + } + + fun update(i: Int, `val`: Int) { + var pos = i + pos += n //这个加n才是tree 数组中位置 + //直接更新 + tree[pos] = `val` + //调整 + + /** + * 如果 pos是偶数 例如修改3 left 是自己,right 是自己+1@author + * + * 如果pos是奇数 例如修改1 left 是自己-1,right 是自己 + * + * 9 8 + * + * 1 3 5 + */ + while (pos > 0) { + var left = pos + var right = pos + if (pos % 2 == 0) { + right = pos + 1 + } else { + left = pos - 1 + } + // parent is updated after child is updated + tree[pos / 2] = tree[left] + tree[right] + pos /= 2 + } + + } + + fun sumRange(i: Int, j: Int): Int { +// get leaf with value 'l' + var l = i + var r = j + l += n//获取tree中坐标 + // get leaf with value 'r' + r += n + var sum = 0 + while (l <= r) { + + + if ((l % 2) == 1) { + println(">>>"+tree[l]) + sum += tree[l] + l++ + } + if ((r % 2) == 0) { + println(">>>>>>"+ tree[r]) + sum += tree[r] + r-- + } + l /= 2 + r /= 2 + } + return sum + + } + +} + + +/** + * 一般方式 sumRang 时间复杂度为O(n) + * +private int[] nums; +public int sumRange(int i, int j) { +int sum = 0; +for (int l = i; l <= j; l++) { +sum += data[l]; +} +return sum; +} + +public int update(int i, int val) { +nums[i] = val; +} + */ + +/** +sqrt 分解 +其思想是将数组分割成块,块的长度为 sqrt n +​ +。然后我们计算每个块的和,并将其存储在辅助存储器 b 中。要查询 RSQ(i, j),我们将添加位于内部的所有块和部分在范围 [i\ldots j][i…j] 重叠的块的总和。 + + + */ \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode307_4.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode307_4.java new file mode 100644 index 0000000..ccb00d7 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode307_4.java @@ -0,0 +1,338 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.ArrayList; +import java.util.List; + +class NumArray4 { + + public static void main(String[] args) { + int value[] = new int[]{0,-3,-3,1,1,2}; + NumArray4 numArray = new NumArray4(value); + System.out.println("sumRange结果=" + numArray.sumRange(3, 5)); + +// System.out.println("" + numArray.countRange(0, 1)); + + } + + private int[] b; + private int len; + private int[] nums; + SegementTree tree; + + public NumArray4(int[] nums) { + + Merger merger = new Merger() { + @Override + public Integer merge(Integer a, Integer b) { + return a + b; + } + }; + + Integer data[] = new Integer[nums.length]; + for (int i = 0; i < nums.length; i++) { + data[i] = nums[i]; + } + tree = new SegementTree(data, merger); + tree.printSegementTree(); +// System.out.println("区间数量:" + tree.countRangeSum(1, 3)); + + } + + public int sumRange(int i, int j) { + return tree.query(i, j); + } + + public void update(int i, int val) { + tree.set(i, val); + } + + public int countRange(int i, int j) { + return tree.countRangeSum(i, j); + } + + + public interface Merger { + + /** + * 合成方法,a和b代表一个父节点下的两个子节点的值 + * + * @param a + * @param b + * @return 根据a和b,计算出的父节点对应的值 + */ + public E merge(E a, E b); + + } + + public static class SegementTree { + + /** + * 线段树中传入的值,存储的副本 + */ + public E[] data; + + /** + * 线段树中的节点,其中父节点的值为它的两个子节点merge后的值 + */ + public E[] tree; + + /** + * 合成器,构造线段树时候同时传入合成器 + */ + public Merger merger; + + /** + * 构造线段树 + * + * @param data 传入的数据 + * @param merger 传入的合成器 + */ + public SegementTree(E[] data, Merger merger) { + if (data.length == 0) { + return; + } + this.merger = merger; + int length = data.length; + this.data = (E[]) new Object[length]; + //复制数据到data中 + for (int i = 0; i < length; i++) { + this.data[i] = data[i]; + } + //总共n个叶子节点,n-1个非叶子节点 + tree = (E[]) new Object[length * 4]; + //构造线段树 + buildSegementTree(0, 0, length - 1); + } + + /** + * 构造线段树中的tree中的节点 + * + * @param treeIndex tree中对应节点的index + * @param left 这个节点对应data中的范围的左边界,root对应0 + * @param right 这个节点对应data中的范围的右边界,root对应length-1 + *

+ *

+ *

+ * 由根节点出发构建子树,最终通过回溯,构建父节点 + * 通常比较难理解的就是数组中元素位置和树节点对应关系, + *

+ * 首先从根节点出发(也就是tree 0元素)递归去为每个节点赋值 + * 递归一次计算左右孩子节点的index,递归一次相当于数组被分成了两份,所以除以二,其中奇数时算在左区间中 + * 递归终止条件就是被拆分的数组 left == right,说明不可拆分,此时将tree对应index的数组修改成这个不可分的数组值, + * 其他节点也是这样不相等就继续分,因为叶子节点只能保存一个值,最终全部分配到叶子节点后向上回溯父节点的值 + *

+ * 这里通过一个合成器,来动态定义父节点的赋值策略,可以使两个节点的和,也可以是两个节点中最大值 + *

+ * 比如 一个根节点 放3个数组,放不下,继续拆分, 其中有节点 放[2,2]这只有一个元素,直接存储 + * 左边[0,1]放不下,继续才分,[0,0][1,1] 可以放下了,结束 + */ + public void buildSegementTree(int treeIndex, int left, int right) { + if (left == right) { + //如果left==right,证明递归结束,在对应的index设置data里left的值 + System.out.println("size" + tree.length + "treeIndex=" + treeIndex + "left=" + left); + tree[treeIndex] = data[left]; + return; + } + //tree中父节点为treeIndex,的左右孩子的index + int leftChildIndex = getLeftChild(treeIndex); + int rightChildIndex = getRightChild(treeIndex); + int mid = left + (right - left) / 2;//如果偶数左边右边一样多,如果是奇数,算到左边中 + //构造左右孩子节点 + buildSegementTree(leftChildIndex, left, mid); + buildSegementTree(rightChildIndex, mid + 1, right); + //根据左右孩子的值,通过合成器,决定父节点的值 + tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]); + } + + /** + * 返回左孩子在数组中的位置 + * + * @param index 父节点的index + * @return 左孩子节点的index + */ + public int getLeftChild(int index) { + //可以这样看,root节点,index:0 + //root的左孩子,index:1 + //root的右孩子,index:2 + //root的左孩子的左孩子,index:3 + //root的左孩子的有孩子,index:4 + return 2 * index + 1; + } + + /** + * 返回右孩子在数组中的位置 + * + * @param index 父节点的index + * @return 右孩子节点的index + */ + public int getRightChild(int index) { + return 2 * index + 2; + } + + /** + * 打印线段树 + */ + public void printSegementTree() { + System.out.println("开始打印线段树----------"); + System.out.println("线段树数据的长度为" + data.length); + for (int i = 0; i < tree.length; i++) { + System.out.println("位置" + i + ": " + tree[i]); + } + + System.out.println("打印线段树结束----------"); + } + + /** + * 返回data中区间left和right间,对应的值 + * + * @param left + * @param right + * @return + */ + public E query(int left, int right) { + if (left < 0 || right < 0 || left >= data.length || right >= data.length || left > right) { + return null; + } + return queryRange(0, 0, data.length - 1, left, right); + + } + + /** + * 统计区间数量,也就是节点数量 + * + * @param left + * @param right + * @return + */ + public int countRangeSum(int left, int right) { + int l = left; + int r = right; + if (left > right) { + return 0; + } + if (right < 0) { + return 0; + } + if (r >= data.length) { + r = data.length - 1; + } + if (l < 0) { + l = 0; + } + + List posList = new ArrayList(); + queryCount(0, 0, data.length - 1, l, r, posList); + return posList.size(); + } + + + public E queryCount(int treeIndex, int treeLeft, int treeRight, int queryLeft, int queryRight, List posList) { + + + if (treeLeft == queryLeft && treeRight == queryRight) { + //如果该节点的范围正好对应查询范围,直接返回 + System.out.println("********1"); + posList.add(treeIndex); + return tree[treeIndex]; + } + int leftChildIndex = getLeftChild(treeIndex); + int rightChildIndex = getRightChild(treeIndex); + int mid = treeLeft + (treeRight - treeLeft) / 2; + if (queryLeft >= mid + 1) { + //如果查询范围仅仅对应左孩子或者右孩子 + return queryCount(rightChildIndex, mid + 1, treeRight, queryLeft, queryRight,posList); + } else { + if (queryRight <= mid) { + return queryCount(leftChildIndex, treeLeft, mid, queryLeft, queryRight,posList); + } + } + //查询范围,左右孩子都有 + E resultLeft = queryCount(leftChildIndex, treeLeft, mid, queryLeft, mid,posList); + E resultRight = queryCount(rightChildIndex, mid + 1, treeRight, mid + 1, queryRight,posList); + //最终结果是左右孩子的合并 + E result = merger.merge(resultLeft, resultRight); + System.out.println("********2"); + posList.add(treeIndex); + return result; + } + + /** + * 在以tree中位置为treeIndex为根节点,而且该节点对应的data中的范围为[treeLeft,treeRight]
+ * 查询范围为[queryLeft,queryRight]对应的值 + * + * @param treeIndex + * @param treeLeft + * @param treeRight + * @param queryLeft + * @param queryRight + * @return + */ + public E queryRange(int treeIndex, int treeLeft, int treeRight, int queryLeft, int queryRight) { + if (treeLeft == queryLeft && treeRight == queryRight) { + //如果该节点的范围正好对应查询范围,直接返回 + System.out.println("********1"); + return tree[treeIndex]; + } + int leftChildIndex = getLeftChild(treeIndex); + int rightChildIndex = getRightChild(treeIndex); + int mid = treeLeft + (treeRight - treeLeft) / 2; + if (queryLeft >= mid + 1) { + //如果查询范围仅仅对应左孩子或者右孩子 + return queryRange(rightChildIndex, mid + 1, treeRight, queryLeft, queryRight); + } else { + if (queryRight <= mid) { + return queryRange(leftChildIndex, treeLeft, mid, queryLeft, queryRight); + } + } + //查询范围,左右孩子都有 + E resultLeft = queryRange(leftChildIndex, treeLeft, mid, queryLeft, mid); + E resultRight = queryRange(rightChildIndex, mid + 1, treeRight, mid + 1, queryRight); + //最终结果是左右孩子的合并 + E result = merger.merge(resultLeft, resultRight); + System.out.println("********2"); + return result; + } + + + /** + * 在线段树中修改data中index的元素,设置新的值为value + * + * @param index + * @param value + */ + public void set(int index, E value) { + if (index < 0 || index >= data.length) { + return; + } + setValue(0, 0, data.length - 1, index, value); + } + + /** + * 在以tree中位置为treeIndex为根节点,而且该节点对应的data中的范围为[treeLeft,treeRight] 下,
+ * 修改data中index的元素,设置新的值为value + * + * @param treeIndex + * @param treeLeft + * @param treeRight + * @param index + * @param value + */ + public void setValue(int treeIndex, int treeLeft, int treeRight, int index, E value) { + if (treeLeft == treeRight) { + tree[treeIndex] = value; + return; + } + int leftChildIndex = getLeftChild(treeIndex); + int rightChildIndex = getRightChild(treeIndex); + int mid = treeLeft + (treeRight - treeLeft) / 2; + if (index <= mid) { + setValue(leftChildIndex, treeLeft, mid, index, value); + } else { + setValue(rightChildIndex, mid + 1, treeRight, index, value); + } + tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]); + } + + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode315.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode315.kt new file mode 100644 index 0000000..7f7055b --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode315.kt @@ -0,0 +1,5 @@ +package com.wangpos.datastructure.leetcode + +fun main() { + // +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode319.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode319.kt new file mode 100644 index 0000000..d0f4ff4 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode319.kt @@ -0,0 +1,17 @@ +package com.wangpos.datastructure.leetcode + +import java.lang.Math.sqrt +import kotlin.math.sqrt + +fun main() { + + val result = bulbSwitch(3) + + + println(result) +} + +fun bulbSwitch(n: Int): Int { +// return sqrt(n.toDouble()).toInt() + return 0 +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode327.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode327.java new file mode 100644 index 0000000..19aaaa9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode327.java @@ -0,0 +1,141 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.ArrayList; +import java.util.List; + + +class Solution { + public int countRangeSum(int[] nums, int lower, int upper) { + if(nums.length==0){ + return 0; + } + LeetCode327 leetCode327 = new LeetCode327(nums); + return leetCode327.countRange(lower, upper); + } +} + +public class LeetCode327 { + SegementTree tree; + + public LeetCode327(int[] nums) { + + Merger merger = new Merger() { + @Override + public Integer merge(Integer a, Integer b) { + return a + b; + } + }; + + Integer data[] = new Integer[nums.length]; + for (int i = 0; i < nums.length; i++) { + data[i] = nums[i]; + } + tree = new SegementTree(data, merger); + tree.printSegementTree(); + + } + + public int countRange(int i, int j) { + return tree.countRangeSum(i, j); + } + + + public interface Merger { + public E merge(E a, E b); + } + + public static class SegementTree { + public E[] data; + + public E[] tree; + + public Merger merger; + + public SegementTree(E[] data, Merger merger) { + if (data.length == 0) { + return; + } + this.merger = merger; + int length = data.length; + this.data = (E[]) new Object[length]; + //复制数据到data中 + for (int i = 0; i < length; i++) { + this.data[i] = data[i]; + } + //总共n个叶子节点,n-1个非叶子节点 + tree = (E[]) new Object[length * 4]; + //构造线段树 + buildSegementTree(0, 0, length - 1); + } + + public void buildSegementTree(int treeIndex, int left, int right) { + if (left == right) { + //如果left==right,证明递归结束,在对应的index设置data里left的值 + tree[treeIndex] = data[left]; + return; + } + //tree中父节点为treeIndex,的左右孩子的index + int leftChildIndex = getLeftChild(treeIndex); + int rightChildIndex = getRightChild(treeIndex); + int mid = left + (right - left) / 2;//如果偶数左边右边一样多,如果是奇数,算到左边中 + //构造左右孩子节点 + buildSegementTree(leftChildIndex, left, mid); + buildSegementTree(rightChildIndex, mid + 1, right); + //根据左右孩子的值,通过合成器,决定父节点的值 + tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]); + } + + public int getLeftChild(int index) { + return 2 * index + 1; + } + + public int getRightChild(int index) { + return 2 * index + 2; + } + + public void printSegementTree() { + System.out.println("开始打印线段树----------"); + System.out.println("线段树数据的长度为" + data.length); + for (int i = 0; i < tree.length; i++) { + System.out.println("位置" + i + ": " + tree[i]); + } + + System.out.println("打印线段树结束----------"); + } + + public int countRangeSum(int left, int right) { + int l = left; + int r = right; + + List posList = new ArrayList(); + queryCount(0, 0, data.length - 1, l, r, posList); + return posList.size(); + } + + public void queryCount(int treeIndex, int left, int right, int l, int r, List posList) { + if (left == right) { + //如果left==right,证明递归结束,在对应的index设置data里left的值 +// System.out.println("size>>>>>" + tree[treeIndex]); + tree[treeIndex] = data[left]; + if ((Integer) tree[treeIndex] >= l && (Integer) tree[treeIndex] <= r) { + posList.add(treeIndex); + } + return; + } + //tree中父节点为treeIndex,的左右孩子的index + int leftChildIndex = getLeftChild(treeIndex); + int rightChildIndex = getRightChild(treeIndex); + int mid = left + (right - left) / 2;//如果偶数左边右边一样多,如果是奇数,算到左边中 + //构造左右孩子节点 + queryCount(leftChildIndex, left, mid, l, r, posList); + queryCount(rightChildIndex, mid + 1, right, l, r, posList); + //根据左右孩子的值,通过合成器,决定父节点的值 + if ((Integer) tree[treeIndex] >= l && (Integer) tree[treeIndex] <= r) { + posList.add(treeIndex); + } + tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]); +// System.out.println("size>>" + tree[treeIndex]); + } + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode327.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode327.kt new file mode 100644 index 0000000..551e6bb --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode327.kt @@ -0,0 +1,4244 @@ +package com.wangpos.datastructure.leetcode + +import com.wangpos.datastructure.algorithm.search +import java.util.* + +fun main() { + //为什么int型最大的数是2147483647 + val value = intArrayOf( + 13, + 6, + 13, + 4, + 27, + -14, + -13, + 15, + -4, + 13, + -15, + 21, + -23, + 19, + 3, + -14, + -27, + -17, + -4, + 15, + 1, + -27, + -17, + -12, + -26, + 12, + 11, + -24, + 9, + -18, + 18, + 26, + -5, + -25, + -29, + -26, + 17, + -24, + -22, + 27, + -2, + 16, + -7, + -17, + 15, + -10, + -24, + -14, + -19, + -22, + -1, + -9, + -30, + -11, + 20, + -14, + -5, + 4, + 20, + 7, + -2, + 20, + 26, + 16, + -13, + -10, + 3, + -29, + 13, + 26, + 14, + -2, + -13, + 18, + 28, + 9, + -11, + 17, + -28, + -4, + -23, + -23, + -7, + -22, + 11, + 2, + -24, + -4, + -11, + 9, + -23, + 29, + 9, + -10, + -17, + -16, + -11, + 2, + -28, + 3, + 12, + -8, + -16, + -16, + -25, + 27, + 21, + -17, + -5, + 17, + -17, + -30, + 2, + -13, + -14, + -2, + -15, + -26, + -12, + 9, + 12, + -26, + 19, + 21, + 10, + -10, + 9, + -15, + 5, + -1, + -14, + 19, + -18, + -30, + 15, + -9, + -19, + -11, + -20, + 14, + -9, + 18, + -19, + -9, + -18, + 29, + 14, + -29, + 11, + -9, + -7, + -21, + 12, + -17, + -11, + -23, + -8, + 1, + -1, + -5, + -22, + -26, + -18, + 27, + 6, + -24, + -27, + 21, + -25, + -22, + 17, + 18, + 28, + 20, + -7, + 19, + 6, + -11, + 14, + 10, + 6, + -22, + -4, + 13, + 5, + 5, + 3, + -10, + 0, + -6, + -29, + 2, + -22, + 10, + -26, + 3, + -23, + 29, + 28, + 4, + 0, + -26, + 13, + -1, + 4, + -13, + 25, + 23, + 27, + -30, + 5, + -21, + 26, + -12, + 4, + 4, + 23, + 4, + -27, + 15, + 5, + 15, + 11, + -1, + 0, + -28, + 8, + -26, + -22, + 27, + -3, + 6, + -19, + -28, + 27, + 8, + -13, + -30, + 3, + 25, + -9, + 2, + -5, + 26, + -2, + 7, + 11, + -22, + 26, + -6, + 8, + 22, + 24, + 7, + 8, + 8, + 28, + -22, + -12, + 5, + -2, + 1, + -8, + -30, + -20, + 4, + -17, + 22, + -6, + -25, + -4, + 27, + 20, + 25, + 8, + 14, + -26, + 20, + 3, + 1, + 18, + -27, + 3, + 4, + -20, + 9, + -30, + 3, + -26, + -21, + 0, + -24, + -20, + -18, + -28, + -28, + -18, + -25, + -14, + -3, + -15, + 25, + 3, + -16, + -24, + -13, + 13, + -12, + 27, + -26, + 9, + 15, + 23, + 17, + -14, + -20, + -2, + 15, + 0, + 3, + 6, + 9, + 26, + 18, + -11, + 27, + 19, + 16, + 3, + 9, + 7, + -12, + 15, + 7, + 26, + 28, + 1, + -3, + -19, + 27, + -27, + -29, + 8, + 22, + 9, + -15, + -3, + -17, + 25, + 15, + -16, + 20, + -28, + 28, + 22, + 28, + 16, + -27, + -21, + -17, + -17, + 24, + -10, + -16, + -27, + -18, + -25, + -13, + 25, + 11, + 28, + -7, + 7, + 6, + 23, + -27, + -19, + 13, + 25, + -23, + -14, + -21, + 4, + -10, + 0, + -24, + 26, + 5, + -6, + 6, + -27, + 23, + -30, + 10, + 19, + -12, + 10, + 15, + -5, + -15, + -2, + 7, + -1, + -17, + -14, + 10, + 29, + -24, + -7, + 2, + 21, + -19, + -8, + -27, + -3, + 22, + -5, + -6, + 8, + 26, + 10, + -17, + 12, + -6, + 9, + 3, + 7, + -26, + -26, + 4, + 6, + 2, + 23, + 7, + 13, + -20, + -19, + 5, + 3, + 0, + -10, + -21, + 25, + -20, + -20, + -22, + 6, + -18, + 19, + 22, + -13, + -21, + 17, + 4, + -22, + 19, + 6, + 5, + 25, + -17, + 6, + 18, + 19, + 11, + 23, + 3, + 6, + 22, + 24, + -15, + -27, + -5, + -12, + -27, + 14, + -22, + 26, + -26, + -11, + -20, + -7, + 18, + 19, + 23, + -25, + 17, + -27, + 22, + 22, + -13, + -23, + -6, + -18, + 25, + 7, + -13, + -24, + -6, + -1, + 18, + 13, + -12, + 3, + 11, + -20, + 10, + -13, + 18, + 25, + -7, + -8, + -17, + 17, + -3, + 5, + 16, + 23, + 17, + -18, + 27, + 26, + -10, + 20, + 5, + -11, + 5, + -29, + 18, + 29, + 12, + 2, + -21, + -11, + -4, + 12, + -5, + 24, + 5, + -20, + 25, + 9, + 3, + -8, + 2, + 19, + 20, + 24, + -1, + -3, + -2, + -27, + 19, + 24, + 28, + 13, + 17, + 16, + 13, + -12, + -8, + 5, + -14, + 21, + -2, + -25, + 6, + 9, + -13, + 1, + -12, + 26, + -13, + 15, + -21, + 13, + 4, + 16, + 5, + -24, + 15, + -14, + 15, + 14, + -28, + 21, + 1, + -8, + 21, + -25, + -5, + -23, + -15, + -18, + -25, + -24, + 7, + 25, + -18, + -22, + 15, + 4, + -25, + -17, + -9, + -1, + 15, + 6, + 26, + 22, + 15, + 17, + -22, + -2, + 15, + -28, + -16, + -14, + -20, + 17, + -18, + -4, + 6, + 17, + -19, + 7, + 0, + -3, + -26, + 15, + 12, + -5, + -25, + -21, + 26, + 17, + 14, + -28, + 13, + 13, + 29, + 2, + 7, + -12, + 1, + 28, + -1, + -20, + -30, + -15, + 5, + 25, + -20, + -18, + -15, + -25, + -23, + 24, + 16, + -2, + -26, + -22, + 16, + -20, + -5, + -20, + 18, + -28, + -24, + 7, + -17, + -12, + -22, + 17, + 29, + -8, + 11, + 25, + -4, + -26, + -18, + 20, + -23, + -29, + 27, + 27, + 17, + 20, + 7, + -11, + -9, + -26, + -5, + 5, + -6, + -16, + -4, + 13, + 11, + -6, + -20, + 19, + -9, + 8, + 12, + -15, + 3, + 25, + -23, + 0, + -24, + 6, + -24, + -23, + -1, + -5, + 22, + -29, + 7, + -20, + -7, + 18, + -18, + -18, + 15, + 17, + -7, + -12, + 29, + -12, + -28, + -11, + -25, + 28, + 19, + -16, + -25, + 23, + -25, + 8, + -13, + -18, + -28, + -17, + -15, + 6, + -5, + -27, + 18, + 15, + -11, + -30, + 6, + 23, + 8, + -4, + 4, + -14, + -29, + 12, + 18, + 11, + 1, + 12, + 4, + 24, + 5, + -14, + -29, + -4, + -11, + 4, + 16, + -30, + -16, + 23, + -21, + -19, + 6, + 26, + 22, + 19, + -8, + 27, + -14, + -8, + -6, + -30, + -26, + 27, + -26, + -28, + -25, + 8, + 10, + -4, + -15, + 0, + 22, + -20, + -20, + 10, + -17, + -11, + 13, + 9, + 8, + 24, + -16, + -16, + -8, + 25, + 3, + 22, + -8, + 24, + -6, + 9, + -29, + 20, + -9, + 24, + -8, + 23, + -8, + 22, + -4, + 15, + 0, + 19, + 10, + 27, + 26, + 23, + 20, + 15, + 18, + -22, + 4, + -18, + -18, + -30, + 7, + -21, + 5, + -12, + 25, + -9, + -4, + -30, + -4, + 20, + -19, + 22, + 3, + 18, + 17, + 8, + -13, + -17, + 9, + 7, + 25, + 11, + -10, + -27, + -3, + -17, + 10, + -30, + -24, + -29, + -21, + -2, + -3, + 2, + -25, + 26, + -9, + -12, + 18, + -17, + 15, + 20, + 9, + 21, + -24, + 5, + 7, + -28, + 18, + 8, + -11, + 16, + -26, + 3, + 3, + 6, + -16, + -12, + 18, + 4, + -17, + -19, + 10, + 22, + -6, + 20, + 6, + 28, + 2, + -15, + 7, + -20, + 11, + 3, + -22, + -20, + -25, + 28, + 5, + -11, + -23, + 20, + 16, + 23, + -5, + 5, + 8, + -23, + 25, + 6, + 26, + -7, + -13, + -2, + -8, + 29, + 17, + 20, + -14, + -15, + 17, + -20, + -14, + -27, + 21, + 29, + -11, + -20, + 8, + 19, + -18, + -1, + 29, + 5, + 14, + 23, + 15, + 7, + 11, + -15, + 15, + 10, + 9, + 16, + 26, + -8, + -29, + 7, + 6, + -9, + -30, + -6, + 18, + -2, + -10, + 18, + -29, + -1, + -23, + 24, + 23, + -15, + -4, + -3, + -19, + 22, + 18, + -27, + -11, + 14, + 8, + 4, + -6, + 10, + -12, + -10, + 21, + 7, + -15, + -21, + 2, + 12, + 3, + -30, + -9, + -25, + 8, + 19, + 5, + 12, + -12, + -15, + -12, + -9, + 19, + -7, + 17, + 0, + 5, + 17, + -1, + 22, + 13, + -27, + 15, + -28, + -29, + 23, + -19, + 13, + -8, + 23, + 12, + -13, + -15, + -18, + -26, + -22, + -3, + -7, + 2, + -4, + 24, + 2, + 11, + 21, + -27, + 26, + 14, + 13, + 14, + -30, + 19, + -22, + -29, + -1, + -27, + -4, + 25, + -14, + -12, + 4, + -6, + -18, + -11, + -8, + -5, + 29, + 9, + -11, + 22, + -10, + 10, + -8, + 5, + 12, + -21, + 27, + -1, + 28, + -21, + 15, + 26, + -16, + 10, + -17, + 7, + -24, + 0, + -20, + -13, + 3, + 15, + -19, + -18, + -8, + -12, + 20, + 10, + -8, + 25, + 26, + -11, + 23, + 14, + -1, + -7, + -22, + -22, + -16, + -2, + 19, + -27, + 5, + 17, + 28, + 20, + 17, + -12, + -13, + -7, + 22, + 17, + -8, + -2, + 28, + 5, + 29, + -27, + -15, + -2, + 26, + -30, + -6, + -4, + 16, + 25, + 5, + -19, + 1, + -14, + -6, + 29, + -13, + 27, + 20, + 7, + 10, + 21, + 12, + 10, + -12, + -21, + -10, + -2, + -18, + -11, + -15, + 1, + -11, + 21, + 6, + 26, + 20, + -24, + 29, + 6, + 26, + -5, + -18, + -12, + -16, + -11, + 11, + -24, + 4, + 13, + 15, + -9, + -3, + 17, + -16, + 5, + -17, + -25, + -30, + -21, + 15, + -16, + 27, + -5, + -4, + -25, + 28, + 7, + 17, + 27, + -18, + -20, + -9, + 13, + 3, + -12, + -16, + -23, + 19, + -9, + 10, + 0, + -22, + -23, + 10, + -20, + -4, + 25, + 28, + -8, + -20, + 7, + -1, + -26, + -12, + 8, + -22, + 24, + 21, + -15, + -26, + -5, + 10, + 24, + -27, + -5, + -1, + -30, + 29, + 23, + 28, + -10, + -27, + -16, + 13, + -1, + 12, + -10, + 13, + 14, + -30, + -5, + -25, + 19, + -19, + 13, + 26, + 27, + 3, + 3, + 28, + 3, + -9, + 8, + 6, + 23, + -2, + -9, + -26, + 22, + -2, + 19, + 12, + -19, + -15, + 15, + 9, + -29, + -24, + 27, + -1, + -15, + -17, + 11, + 11, + -1, + 14, + -29, + -19, + 24, + -29, + -4, + -15, + -26, + -21, + 9, + -1, + -22, + -17, + -9, + -28, + -1, + -29, + 9, + 25, + 13, + 14, + -29, + -23, + -22, + 1, + -14, + 26, + -27, + -14, + 12, + 13, + 11, + 1, + 14, + -1, + 11, + -14, + -16, + -3, + 5, + 2, + -8, + -12, + 0, + 17, + -5, + -6, + -6, + 25, + -19, + 21, + 25, + 12, + 4, + 21, + -9, + 8, + -11, + -30, + 26, + 19, + 15, + 20, + 18, + -13, + 0, + -30, + 8, + -16, + -2, + -17, + 2, + 17, + -21, + -25, + 5, + 26, + 26, + 23, + -6, + -28, + 11, + -16, + 1, + -5, + 13, + -14, + 7, + 0, + -10, + -30, + -16, + -29, + -25, + 12, + -15, + -9, + -2, + 15, + 4, + -18, + 27, + 19, + -26, + 4, + -17, + 18, + -15, + -24, + 24, + -17, + -16, + 19, + -30, + -20, + 13, + 20, + 18, + -14, + -26, + -22, + 10, + 23, + 27, + -28, + 5, + -6, + -21, + 26, + -3, + 10, + 1, + 26, + -18, + 9, + 16, + -6, + 13, + -29, + 8, + 6, + -30, + -1, + 20, + -30, + 0, + 23, + 1, + -23, + 20, + -25, + -29, + 29, + 29, + 21, + -13, + -5, + 3, + -16, + -18, + 24, + 7, + 27, + -27, + -11, + -21, + 14, + 10, + 0, + 15, + 26, + -9, + 23, + -13, + -6, + 22, + -9, + -9, + -6, + -7, + -25, + 6, + -7, + 19, + 22, + -24, + 28, + 29, + 4, + 16, + 28, + 23, + 0, + -3, + 21, + -1, + 3, + -25, + -19, + -19, + 17, + -22, + 9, + 28, + -11, + 28, + -4, + -3, + -25, + 21, + -23, + -30, + 10, + 25, + -18, + 10, + 17, + -15, + 27, + 19, + 3, + 18, + -30, + 29, + 6, + -26, + 10, + 27, + 21, + 1, + -26, + -20, + -10, + -6, + 26, + -3, + -13, + 0, + -2, + 22, + 4, + -28, + -8, + -26, + -29, + 8, + -12, + -13, + 29, + 4, + 3, + 3, + -12, + -7, + 28, + 26, + -29, + 28, + -20, + -15, + -19, + 26, + 26, + -27, + 14, + -5, + -6, + 21, + -28, + 15, + -30, + -20, + -26, + -7, + -26, + 27, + 24, + -9, + -10, + -21, + 26, + -5, + -16, + 0, + 23, + -3, + -20, + -5, + 15, + -10, + -4, + -2, + 16, + 17, + -17, + -3, + -22, + -27, + 25, + 28, + -26, + -6, + -14, + -8, + -19, + -24, + -8, + 12, + -26, + -13, + -7, + 10, + 29, + -7, + -10, + -30, + 13, + 15, + 21, + -1, + 5, + 7, + 1, + 8, + 7, + -2, + -13, + 15, + -19, + 15, + 23, + 10, + 20, + 28, + 22, + -2, + -1, + 25, + -22, + 9, + -22, + 3, + -8, + -5, + 10, + 18, + -7, + 2, + 4, + -1, + -4, + -17, + 29, + 29, + 29, + -27, + -16, + -18, + -14, + -3, + -12, + 19, + -9, + -1, + -2, + -14, + 16, + -15, + 7, + -4, + 24, + -5, + -14, + -29, + -21, + -21, + 12, + 17, + 20, + -10, + -3, + -16, + 19, + 7, + -28, + -22, + -30, + -28, + -3, + -23, + 11, + 8, + 14, + 11, + 19, + 24, + -12, + -25, + 10, + 2, + -13, + 9, + 10, + -12, + -8, + 20, + -14, + 14, + 8, + 24, + -28, + 0, + 1, + 14, + -28, + 2, + -6, + 4, + -19, + -19, + -28, + 21, + -3, + 15, + -28, + 25, + -27, + 8, + 25, + -29, + -17, + 9, + -21, + -20, + -21, + 6, + -22, + -6, + -30, + -13, + -27, + 25, + 21, + 26, + 6, + -19, + -1, + 29, + -30, + -23, + 29, + 12, + -5, + -8, + -25, + 19, + 18, + -1, + 25, + -9, + 3, + 15, + 0, + -2, + 28, + -19, + -24, + -17, + 26, + -12, + 17, + 23, + 7, + 21, + -25, + 2, + 6, + 10, + 18, + -4, + 21, + -14, + -8, + -27, + 24, + 26, + -2, + 20, + -29, + -14, + 13, + 16, + 21, + -26, + -9, + -20, + 9, + -5, + -7, + 13, + -26, + 2, + -30, + 13, + -12, + 12, + -27, + 27, + -9, + -19, + 7, + -9, + -2, + -5, + -16, + 29, + 25, + 26, + 0, + 6, + 27, + -22, + -1, + -22, + -1, + 13, + -10, + 15, + -29, + 24, + 14, + 27, + -28, + -16, + 10, + 3, + 9, + -22, + -8, + -22, + -25, + -7, + -3, + 5, + 26, + -28, + 14, + 15, + 28, + 4, + 4, + 20, + -5, + -13, + 20, + -30, + 5, + -25, + -23, + -11, + -14, + -14, + 14, + 21, + 3, + -25, + -21, + 19, + -19, + 11, + -28, + 9, + -4, + -30, + -3, + -18, + -8, + -15, + -6, + -10, + -17, + -23, + -2, + 22, + -1, + -11, + 28, + 9, + 3, + -20, + -6, + 8, + 5, + 14, + 26, + 24, + -22, + -17, + -24, + -15, + -10, + 19, + 13, + -10, + -8, + 21, + -7, + 12, + -19, + 25, + 16, + 4, + -7, + 28, + 23, + -30, + -2, + 4, + 27, + 8, + 13, + -23, + 3, + 24, + -15, + -20, + 10, + 2, + 11, + 7, + 21, + -2, + 20, + -30, + 19, + 11, + 5, + 5, + -29, + 21, + 28, + -18, + 19, + -28, + 29, + 9, + -18, + -26, + -12, + 12, + -15, + -6, + -13, + 29, + 23, + 7, + -23, + -19, + -30, + -11, + -11, + -25, + 19, + -26, + -28, + -28, + 25, + -2, + 29, + 6, + 3, + -19, + -18, + 27, + 20, + 23, + 25, + -27, + 27, + -30, + -25, + 28, + 13, + -25, + -30, + 1, + -14, + -9, + -15, + 27, + 2, + 28, + 28, + -20, + 8, + 1, + -19, + -8, + -4, + 6, + 10, + -11, + 27, + -18, + -9, + 27, + 7, + -1, + -12, + -16, + 3, + -28, + -8, + -22, + 20, + -5, + -16, + 16, + 17, + 20, + -29, + 22, + -3, + -1, + -5, + -11, + 18, + 2, + -12, + 8, + 4, + 15, + -14, + 5, + 25, + 17, + -18, + -17, + -12, + -10, + 18, + -7, + 8, + -30, + -22, + -28, + -10, + -30, + 14, + -5, + -9, + 16, + -24, + -16, + 0, + 2, + 4, + -14, + 9, + -2, + 1, + -6, + -7, + 10, + -20, + -1, + 0, + 25, + 27, + -24, + 3, + -10, + 27, + -8, + 12, + 8, + 6, + 16, + -14, + -24, + 4, + 5, + 12, + -7, + -9, + 8, + 3, + 9, + 10, + -8, + 24, + 12, + -13, + 8, + 10, + -23, + 4, + 10, + 16, + 20, + 7, + 8, + -29, + -11, + -29, + 21, + 23, + 20, + 23, + 12, + -26, + -14, + 10, + -2, + 21, + -29, + 5, + 18, + 10, + -27, + -11, + -22, + -25, + 1, + 14, + -12, + 1, + -26, + -11, + -13, + -2, + 7, + 10, + -24, + -15, + -26, + 1, + 13, + 23, + 8, + -26, + 29, + 8, + -13, + -11, + -21, + 5, + -25, + 13, + -7, + 20, + 28, + 5, + 25, + 22, + -23, + 7, + -21, + 4, + -8, + 9, + -24, + 13, + 22, + 19, + 24, + -27, + 2, + 23, + 28, + -22, + 0, + 14, + 3, + -27, + -1, + -17, + 27, + 12, + -19, + 13, + 3, + 23, + 0, + -26, + 28, + -15, + 0, + 2, + -11, + 4, + -26, + 3, + 8, + -15, + -12, + 2, + -12, + -25, + 15, + 4, + 4, + 11, + -18, + 9, + 6, + 5, + 26, + -11, + -22, + 6, + 17, + -23, + -14, + -7, + -24, + -28, + -25, + -3, + -15, + 18, + 10, + -20, + 15, + -17, + -16, + 11, + 22, + 13, + 4, + 6, + -10, + -12, + 28, + 15, + 0, + 19, + -30, + -5, + 8, + 16, + -23, + 4, + -18, + 10, + -1, + -25, + -8, + -21, + -13, + 5, + -19, + -8, + -6, + 6, + 0, + 13, + 7, + 17, + 12, + -3, + -17, + -21, + 16, + -17, + -3, + 13, + 10, + -27, + -1, + -16, + 9, + 18, + -1, + 5, + -19, + 11, + 14, + -12, + -3, + -1, + -16, + 22, + -9, + 19, + -11, + 28, + -14, + -19, + 2, + 22, + 9, + 9, + 23, + 22, + 0, + 1, + 21, + 5, + -3, + 10, + -3, + -8, + -6, + 13, + 25, + -19, + 26, + 9, + -29, + -26, + 13, + -28, + 20, + -2, + 20, + 12, + 6, + 3, + 22, + -13, + -29, + -5, + -22, + -2, + -14, + -17, + -2, + 7, + 24, + 27, + 17, + -21, + 7, + -21, + 9, + 19, + 26, + -28, + 8, + -20, + -26, + -9, + 22, + -2, + 1, + -9, + -28, + 26, + 15, + -13, + -29, + -25, + -11, + -23, + 2, + 15, + 6, + -14, + -18, + -9, + -4, + -17, + -30, + -12, + 7, + -19, + -8, + 14, + -29, + -17, + 0, + -5, + -29, + 4, + 14, + 25, + -29, + 4, + -6, + 18, + 10, + 7, + 0, + -19, + 21, + 29, + 2, + 21, + 17, + -26, + 16, + 22, + -24, + 18, + 19, + -4, + 19, + 29, + -6, + 0, + 23, + -23, + 17, + 18, + -30, + 4, + 23, + 17, + 17, + 14, + -5, + -9, + -19, + 4, + 23, + 9, + -14, + 23, + 18, + 19, + 4, + 20, + 17, + 24, + 13, + 11, + -3, + -16, + 29, + 9, + 11, + 23, + 26, + -18, + 24, + 7, + 19, + 13, + -20, + 14, + 11, + -4, + 19, + 6, + 22, + -2, + 8, + -5, + -15, + 29, + 14, + 23, + -8, + 16, + -21, + 29, + 5, + 15, + 16, + 3, + 15, + -25, + 27, + -23, + -11, + 15, + 17, + -29, + -27, + 13, + 13, + -11, + 6, + 18, + 9, + -2, + 26, + -11, + -15, + 11, + 5, + 17, + -3, + 25, + 11, + 6, + -20, + -19, + 8, + -7, + -9, + -27, + 22, + -21, + -25, + -28, + -5, + -17, + -24, + -28, + 26, + -3, + 11, + -28, + 21, + -16, + -18, + 4, + -15, + 29, + 7, + 5, + -26, + 22, + -12, + 14, + 14, + 21, + -25, + 29, + -27, + -4, + -29, + 24, + 10, + 27, + 29, + -5, + 21, + -22, + -4, + -6, + -28, + 1, + -24, + -4, + 25, + 11, + -26, + 16, + -13, + -11, + 19, + -15, + 17, + -4, + 10, + -11, + 16, + -18, + -8, + -21, + -8, + -13, + -9, + 5, + -5, + -7, + -29, + -7, + 22, + -23, + -26, + 12, + 13, + 17, + -18, + 14, + 1, + 25, + 28, + 17, + 20, + 15, + -12, + 11, + 5, + -28, + -28, + 3, + 11, + 17, + 23, + -20, + 1, + 23, + -4, + 23, + 13, + -26, + 0, + -12, + 22, + 18, + -19, + -1, + -29, + -12, + -4, + -8, + 9, + -27, + -11, + -9, + 4, + -11, + 2, + -8, + -17, + -27, + -10, + 0, + 18, + -14, + 21, + -11, + -18, + 22, + -18, + -13, + 12, + -22, + 28, + 27, + -30, + 23, + -22, + -2, + -17, + -16, + -2, + -21, + 2, + -4, + 9, + 23, + -13, + -25, + -7, + 6, + -22, + 7, + -29, + -21, + 28, + 1, + -4, + -12, + -12, + -25, + -11, + -8, + 19, + -27, + -5, + -14, + -15, + -24, + 1, + -30, + 29, + 3, + 24, + 8, + -25, + -8, + 7, + 5, + 4, + -13, + 1, + 2, + -30, + -6, + 9, + 17, + -13, + -11, + -26, + 11, + -15, + -5, + -4, + 12, + -28, + -10, + -6, + -16, + 8, + 23, + 5, + 27, + 29, + 5, + 6, + -23, + 8, + 16, + 16, + 0, + -23, + 11, + 27, + 25, + 4, + 15, + 17, + -19, + -27, + -17, + -18, + 24, + 8, + 16, + 11, + 3, + -27, + -5, + -29, + -21, + 2, + -22, + -13, + -17, + 19, + -20, + 24, + -28, + -30, + 0, + 0, + 9, + -16, + 12, + -11, + 9, + 7, + -7, + 10, + -21, + -28, + -13, + 20, + -7, + 22, + -4, + -24, + 14, + 12, + -2, + 0, + -10, + -21, + 12, + -1, + 13, + 10, + -14, + -17, + 3, + -19, + 13, + -10, + 3, + -13, + 10, + 13, + 7, + -8, + -12, + -30, + 12, + -18, + -11, + 24, + -12, + -6, + -2, + -19, + -16, + 19, + 3, + -12, + -19, + 20, + 6, + -28, + 27, + 28, + 12, + -30, + 0, + 28, + -30, + -21, + -17, + -8, + 25, + 10, + 16, + -10, + -11, + 10, + -24, + 4, + 13, + -30, + 3, + -28, + 23, + -5, + -18, + -25, + 12, + -1, + 16, + 12, + -14, + 3, + 29, + 8, + -30, + 21, + 5, + -29, + -21, + -25, + -28, + -8, + 20, + 12, + -22, + -27, + 11, + -11, + 14, + 22, + 2, + 7, + -16, + 21, + -2, + -23, + 20, + -28, + 3, + 29, + -5, + -20, + -22, + 20, + -16, + -7, + 24, + -16, + 20, + 18, + 0, + -15, + -29, + -17, + 5, + -21, + 7, + -30, + 22, + 28, + -29, + 28, + 21, + 10, + -19, + -7, + 27, + 4, + 1, + -2, + 5, + -26, + -14, + 15, + 26, + -13, + 20, + -20, + -23, + -5, + 0, + 18, + 2, + 2, + 19, + 29, + 22, + -20, + -27, + 20, + -14, + -13, + -5, + -10, + 26, + -10, + -7, + -22, + -6, + -12, + 29, + 9, + -14, + -1, + -7, + 25, + 8, + -25, + 12, + -14, + -1, + 22, + 24, + -29, + -16, + -3, + 9, + 26, + 28, + 29, + -10, + 24, + -28, + 9, + 16, + -29, + -16, + 13, + 7, + 6, + 0, + -12, + -3, + -27, + -30, + 18, + 4, + -13, + -14, + -12, + 20, + 1, + 12, + -10, + -6, + -4, + -22, + 11, + -24, + -26, + 22, + -4, + 4, + 21, + -17, + 27, + 5, + 5, + 5, + 14, + -23, + -7, + 24, + -19, + 17, + -6, + 0, + 17, + 15, + -28, + 7, + 28, + -30, + -30, + 24, + 10, + 1, + -3, + 1, + 20, + 5, + 12, + 26, + 12, + -3, + 19, + 7, + 1, + -24, + -5, + 7, + -2, + 10, + -26, + -26, + 7, + 16, + 4, + 2, + 27, + -2, + 17, + -9, + 13, + -5, + -15, + -23, + 5, + -8, + 15, + 24, + -12, + -5, + 2, + -6, + -23, + -24, + -25, + 29, + 22, + -15, + -16, + -21, + 3, + 5, + -7, + 10, + -3, + -13, + -26, + -10, + 24, + 9, + 9, + -17, + -5, + 5, + -26, + 13, + -15, + 5, + -3, + -5, + 10, + 3, + 23, + 3, + -21, + 4, + 25, + 19, + -25, + -15, + -15, + -15, + 3, + 8, + -5, + 25, + -4, + -23, + 10, + 29, + 24, + 1, + 24, + 6, + -17, + 4, + 17, + 5, + -14, + -13, + -1, + -30, + -5, + 21, + -2, + -28, + 10, + -9, + 7, + -25, + 15, + 12, + 3, + 0, + 16, + 22, + 19, + 2, + -4, + -21, + -22, + -2, + -12, + 28, + 15, + 12, + -27, + 27, + -11, + 29, + -10, + 22, + 26, + -26, + -30, + 16, + 0, + 7, + -5, + 4, + 7, + -19, + -24, + -3, + -8, + -10, + 0, + -25, + -26, + -19, + 20, + 20, + 11, + 3, + -18, + 23, + -6, + -2, + -7, + 11, + -20, + -15, + 6, + 26, + 5, + -14, + -11, + -8, + 17, + -30, + 4, + -13, + -3, + -4, + -20, + -4, + 13, + -3, + -22, + 23, + 11, + 27, + 17, + 17, + -19, + -30, + -8, + 10, + -5, + 3, + -6, + -28, + -7, + -15, + -17, + -17, + -27, + 22, + 29, + -6, + 14, + 6, + 23, + -13, + 24, + 3, + -21, + -6, + 14, + -7, + 19, + 14, + -18, + -17, + 7, + -11, + 1, + -24, + -7, + -10, + 0, + 11, + 13, + -20, + -17, + 4, + -16, + -7, + 1, + 15, + -11, + -18, + -23, + -13, + 3, + -10, + 0, + 5, + 15, + -25, + 7, + 4, + 13, + -16, + -12, + 21, + -7, + -7, + -16, + -4, + -7, + 29, + -3, + 26, + -14, + -14, + -1, + 23, + 25, + 4, + -3, + 27, + 3, + 7, + 15, + 21, + -8, + -17, + -30, + 15, + 29, + -6, + -25, + 13, + 17, + 17, + 8, + -10, + -20, + -23, + -27, + 4, + -8, + 0, + 25, + -7, + 4, + 4, + 18, + -26, + 22, + 0, + 10, + 0, + 8, + -26, + -1, + -5, + 15, + 14, + -23, + 7, + 12, + -16, + 16, + 3, + 20, + -27, + -5, + 16, + 0, + -28, + -13, + 26, + -10, + 24, + 20, + -5, + -12, + 14, + 26, + 0, + 19, + 9, + -28, + -26, + 3, + 10, + 3, + -18, + 18, + -29, + -7, + 24, + -26, + 16, + -15, + 19, + 9, + 4, + -12, + -27, + 12, + 14, + -6, + -5, + 12, + 27, + 15, + 16, + 29, + -27, + -4, + -21, + 12, + 15, + 21, + -13, + -19, + 12, + -30, + 14, + -9, + 2, + -13, + -14, + 8, + 7, + 9, + -15, + 0, + 26, + 11, + -23, + -7, + -16, + 26, + 7, + -8, + 19, + 17, + -4, + -11, + 1, + 20, + 13, + 10, + -10, + -9, + -19, + -26, + 27, + 3, + -5, + -12, + 11, + -5, + 18, + -2, + 4, + 18, + 12, + -19, + 7, + -15, + -10, + 7, + 26, + -10, + -10, + -24, + 14, + 11, + -12, + -15, + -19, + -30, + -29, + 12, + -6, + 2, + -16, + -19, + 0, + -16, + 16, + -21, + -14, + 23, + -1, + -5, + -4, + 22, + 22, + 24, + 19, + 5, + 2, + 29, + -13, + 19, + 3, + -29, + 14, + 12, + 27, + 24, + 21, + 9, + 25, + -26, + -9, + 13, + 22, + -5, + -6, + 15, + -17, + 3, + 27, + -18, + 29, + -8, + -10, + -22, + -7, + -14, + -6, + 0, + 24, + 24, + 3, + -22, + -1, + -15, + 21, + 0, + 25, + 4, + -18, + -9, + 10, + -16, + 17, + -16, + 21, + 12, + -30, + -22, + 6, + -8, + -18, + -27, + -27, + 28, + -9, + -2, + -30, + 21, + 5, + 1, + 5, + 11, + -25, + 9, + -6, + -17, + 17, + 8, + -29, + 10, + 0, + -25, + 19, + -30, + -3, + -12, + -4, + 27, + -29, + -1, + 22, + 23, + -21, + 10, + 29, + 28, + 2, + -6, + 12, + -9, + 22, + -12, + -5, + -17, + -25, + -11, + 28, + 3, + -15, + -21, + 20, + 13, + -10, + -12, + 1, + 28, + 25, + -16, + -5, + -7, + -22, + 15, + 26, + -14, + -10, + -24, + 27, + -24, + -24, + 14, + -11, + 7, + -6, + -10, + 8, + 4, + -2, + -26, + -7, + 7, + 20, + -7, + -12, + 11, + -7, + 0, + 1, + 19, + -2, + -15, + 19, + -10, + 23, + 10, + -4, + 3, + 7, + 0, + 23, + -20, + -7, + -14, + 3, + 9, + -6, + -2, + 28, + 26, + 29, + 8, + 4, + 25, + 4, + 23, + -24, + 17, + 0, + -15, + -6, + 29, + 25, + -6, + 19, + -15, + -11, + -20, + -6, + 13, + -9, + 17, + 7, + -9, + 4, + 18, + -25, + 25, + -20, + -3, + 5, + 0, + 3, + 7, + -14, + 23, + 0, + 9, + -26, + -30, + 28, + 24, + -10, + 0, + -28, + -21, + -13, + 23, + -28, + -3, + -19, + -12, + -20, + -24, + 9, + 21, + 21, + -26, + 29, + 12, + 21, + -18, + -19, + 7, + 1, + -1, + -21, + 4, + -12, + 22, + 24, + 20, + 12, + 9, + 7, + 11, + -16, + -27, + 11, + -16, + -7, + 12, + -19, + -16, + -29, + 28, + 24, + -4, + 9, + 21, + 8, + -16, + -2, + 18, + -9, + -22, + -2, + 24, + -1, + 17, + 15, + -18, + 26, + -16, + -27, + 24, + -9, + 26, + 18, + 3, + -12, + -21, + -20, + -13, + 25, + -3, + 16, + -3, + -7, + 18, + -4, + 25, + -22, + -9, + 25, + -21, + -9, + -18, + -13, + 1, + 17, + 20, + 19, + 25, + -22, + 9, + -10, + 1, + 2, + 13, + -12, + 1, + -3, + -3, + 16, + 28, + 15, + 0, + -4, + -12, + -30, + 8, + 1, + -24, + 5, + 28, + 24, + -20, + -2, + 3, + -1, + 0, + -22, + 14, + 27, + 8, + 2, + -12, + -28, + 12, + -18, + 17, + 0, + 16, + -14, + -10, + 22, + -19, + 1, + -13, + -7, + -12, + 25, + 13, + 21, + -13, + -6, + -5, + -19, + 18, + -13, + -28, + -1, + -13, + 22, + 20, + 7, + -9, + -15, + -11, + -17, + -27, + 5, + 21, + 28, + 20, + 12, + 25, + 25, + 4, + -12, + 28, + 17, + -14, + -2, + -6, + -25, + 15, + 29, + 8, + -10, + -5, + 6, + -9, + -29, + -29, + 25, + 9, + -17, + 1, + -4, + -29, + 18, + 23, + 4, + -20, + 14, + 4, + 18, + -13, + 6, + -13, + 19, + -6, + 10, + 21, + -17, + -5, + -16, + 26, + -27, + 3, + 21, + -21, + 28, + 25, + -26, + -28, + 23, + -11, + 15, + 10, + -27, + 19, + -27, + -12, + -17, + 0, + -24, + 8, + 8, + -24, + 0, + 12, + 4, + -27, + -3, + -7, + -26, + 15, + -29, + -27, + -25, + 25, + 20, + 5, + -29, + -23, + 0, + 27, + 23, + 0, + -1, + -2, + 6, + -7, + 13, + 6, + 10, + 28, + 24, + 8, + -14, + -9, + -8, + 24, + -3, + -11, + -1, + 27, + 6, + -29, + 17, + 0, + 19, + 9, + -11, + -7, + -26, + -27, + 10, + 26, + -6, + -10, + -1, + -14, + 19, + 28, + 22, + 19, + 8, + 20, + -3, + 5, + 13, + 21, + -7, + -21, + -25, + 2, + -26, + -17, + 28, + 7, + 25, + -1, + 9, + -14, + -6, + -24, + 26, + -30, + 4, + 26, + -4, + 20, + -21, + -22, + 6, + 17, + -17, + 21, + -25, + -23, + 18, + -13, + 16, + 29, + 8, + -27, + -26, + 9, + -16, + 15, + -30, + -2, + -16, + -7, + 29, + -2, + 2, + -3, + 5, + -21, + 13, + 10, + 5, + 4, + 15, + -19, + 9, + -24, + 28, + 17, + 9, + 21, + -19, + -3, + -1, + 17, + 24, + 5, + -14, + -23, + -8, + -19, + -10, + -7, + 25, + 4, + -6, + 24, + -12, + -19, + 17, + -11, + 26, + 8, + -16, + 28, + -29, + 27, + 15, + 20, + 22, + 15, + 11, + 26, + 9, + -21, + 3, + -6, + -22, + -23, + 14, + 2 + ) + + var value2 = intArrayOf(1, 2, -2, 3) +// val numArray = LeetCode327(value) +// System.out.println("sumRange结果=" + numArray.sumRange(0, 1)); + +// System.out.println("区间数量=" + numArray.countRange(0,0)); +// println("区间数量=" + numArray.countRange(3, 5)) +// println("区间数量=" + numArray.countRange(0, 2)) +// println("区间数量=" + numArray.countRange(-1, 9)) + println(">>>>>>" + countRangeSum(value2, 0, 5)) + + +} + +fun countRangeSum(nums: IntArray, lower: Int, upper: Int): Int { + + + println("start=${System.currentTimeMillis()}") + var p = IntArray(nums.size) //前缀和初始化,前缀和p[x],就是区间数组[0, x)的和 + + p[0] = nums[0] + for (i in 1 until nums.size) { + p[i] = p[i - 1] + nums[i] + + } + +// val index = findLeftIndex(p,lower) + + + p = mergeSort(p, 0, p.size - 1) + println("前缀和=${Arrays.toString(p)}") + var countArray = IntArray(1) + for (i in 0 until p.size) { + val left = p[i] - upper + val right = p[i] - lower + //二分法查找 + println("left=$left") + println("right=$right") + search(i,p, left, right, countArray) + } +// search(p, -5, 6, countArray) +// for(i in 1 until p.size){ +// for(j in i downTo 0 ){ +// +// +// //lower <=ij <= upper +// println("区间$i,$j") +// } +// } + //pi + lower, pi + upper + +// var countArray = IntArray(1) +// for (i in 0 until nums.size) { +//// for (j in 0 until i + 1) { +//// println("区间:$j,$i") +// val rangeSum = findRangeSum(nums, 0, nums.size - 1, lower, upper, countArray) +//// checkNumber(rangeSum, j, i, lower, upper, countArray) +//// } +// } + println("end=${System.currentTimeMillis()}") + return countArray[0] +} + +fun binaryHigh(i:Int,array: IntArray, value: Int): Int { + var low = 0 + var high = i + while (low <= high) { + val middle = (low + high) / 2 + if (value == array[middle]) { + high = middle + break + } + if (value > array[middle]) { + low = middle + 1 + } + if (value < array[middle]) { + high = middle - 1 + } + } + + var next = high+1 + + while (array[next]==array[high]){ + high = next + next +=1 + } + + if(high<0){ + return 0 + } + println("high=$high") + return high +} + +fun binaryLow(i:Int,array: IntArray, value: Int): Int { + var low = 0 + var high = i + + println("$low,$i") + while (low <= high) { + val middle = (low + high) / 2 + if (value == array[middle]) { + return middle + } + if (value > array[middle]) { + low = middle + 1 + } + if (value < array[middle]) { + high = middle - 1 + } + } + println("low=$low") + return low +} + +/** + * 二分法查找 + */ +fun search(i:Int,p: IntArray, leftValue: Int, rightValue: Int, countArray: IntArray) { + + if(leftValue>p[p.size-1]){ + return + } + if(rightValue>l"+leftPosition) + + println(">>r"+rightPosition) + +// lastCountArray[i] = rightPosition - leftPosition + 1 + countArray[0] += rightPosition - leftPosition + 1 + println("""countArray[0=]${countArray[0]}""") +} + +//val lastCountArray = IntArray + +fun mergeSort(nums: IntArray, l: Int, h: Int): IntArray { + if (l == h) + return intArrayOf(nums[l]) + + val mid = l + (h - l) / 2 + val leftArr = mergeSort(nums, l, mid) //左有序数组 + val rightArr = mergeSort(nums, mid + 1, h) //右有序数组 + val newNum = IntArray(leftArr.size + rightArr.size) //新有序数组 + + var m = 0 + var i = 0 + var j = 0 + /** + * 两个数组 前一半比后一半小 + */ + while (i < leftArr.size && j < rightArr.size) { + newNum[m++] = if (leftArr[i] < rightArr[j]) leftArr[i++] else rightArr[j++] + } + //左边剩余 + while (i < leftArr.size) + newNum[m++] = leftArr[i++] + //右边剩余 + while (j < rightArr.size) + newNum[m++] = rightArr[j++] + return newNum +} + +/** + * 统计区间和 + */ +fun findRangeSum( + nums: IntArray, + left: Int, + right: Int, + lower: Int, + upper: Int, + countArray: IntArray +): Long { + if (left == right) { + return nums[left].toLong() + } + + val mid = (left + right) / 2 + + val leftSum = findRangeSum(nums, left, mid, lower, upper, countArray) + val rightSum = findRangeSum( + nums, + mid + 1, + right, + lower, + upper, + countArray + ) + if ((leftSum + rightSum) > Integer.MAX_VALUE || -(leftSum + rightSum) > Integer.MAX_VALUE) { + return Long.MAX_VALUE + } + val sum = leftSum + rightSum + return sum +} + +private fun checkNumber( + sum: Long, + left: Int, + right: Int, + lower: Int, + upper: Int, + countArray: IntArray +) { + if (sum == Long.MAX_VALUE) { + return + } + if (sum in lower..upper) { +// println("合格数据" + sum + "left=$left right=$right") + countArray[0]++ + } else { +// println("不合格数据="+sum) + } + +} + +private fun checkNumber( + nums: IntArray, + left: Int, + right: Int, + lower: Int, + upper: Int, + countArray: IntArray +) { + if (nums[left] in lower..upper) { + println("合格数据 " + nums[left] + "left=$left right=$right") + countArray[0]++ + } else { + println("不合格数据=" + nums[left]) + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode329.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode329.kt new file mode 100644 index 0000000..9fcb1b7 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode329.kt @@ -0,0 +1,154 @@ +package com.wangpos.datastructure.leetcode + +/** + * 矩阵中最长增长路径 + * + * 记忆化: 对于大量重复调用的问题,缓存其结果。 + 动态规划要求按照拓扑顺序解决子问题。对于很多问题,拓扑顺序与自然秩序一致。而对于那些并非如此的问题,需要首先执行拓扑排序。因此,对于复杂拓扑问题(如本题),使用记忆化搜索通常是更容易更好的选择。 + + 想要让动态规划有效,如果问题 B 依赖于问题 A 的结果,就必须确保问题 A 比问题 B先计算。这样的依赖顺序对许多问题十分简单自然。如著名的斐波那契数列 + */ +fun main() { + + /** + * 通过二维数组定义方向 + * + * 比如当前的二维数组中元素为0,0;垂直整下的元素就需要第二位加一,也就是列加一,所以是0,1 + * 同理向上0,-1, 向左 -1,0,向右 1.0 + * + * + */ + + val matrix = arrayOf>(arrayOf(9, 9, 4), arrayOf(6, 6, 8), arrayOf(2, 2, 1)) + val result = longestIncreasingPath(matrix) + + println("结果:${result}") +} + +/** + * 采用深度优先搜索解决此题, + * + * 因为最坏时间是从第一列第一个个元素到第一列最后元素再到最后一行最后一个元素 + * + * 所以相当于第一次搜索 1分为二,第二次二分为4 所以时间复杂度2^n,注意这只是一个点的深度优先搜索,mn 个所以复杂度很高,具体为什么是2^(m+n)本人也没太仔细算 + * + * 时间复杂度是 O(2^(m+n)) + * + * 空间复杂度O(mn) + */ +fun longestIncreasingPath(matrix: Array>): Int { + + if (matrix.size == 0) return 0 + //几列 + m = matrix.size + //几行 + n = matrix[0].size + //最长路径 + var ans = 0 + for (i in 0 until m) { + for (j in 0 until n) { + //每个顶点做四个方向的深度优先搜索 + ans = Math.max(ans, dfs(matrix, i, j)); + } + } + + return ans +} + +/** + * 深度优先搜索 + */ +fun dfs(matrix: Array>, i: Int, j: Int): Int { + //定义本次记录的路径长度 + var ans = 0 + //四个方向相当于图的四个分支,每个方向做深度优先搜索, + for (d in dirs) { + val x = i + d[0]//下一个元素行坐标 + val y = j + d[1]//下一个元素列坐标 + /** + * 校验下标合法范围 + */ + if (0 <= x && x < m && 0 <= y && y < n ){ + //表示递增的 + if( matrix[x][y] > matrix[i][j]){ + //每次查找如果比当前的大就替换 + ans = Math.max(ans, dfs(matrix, x, y)) + } + } + } + //这里+1表示到达当前一步,如果四个方向都没有找到递增的就返回1 + // ,回溯一次多了1,回溯一次多了1,所以每次判断哪个路径回溯的最多, + //最终回溯到终点就是最长路径 ans + return ++ans +} + + +var m = 0 +var n = 0 +val dirs = arrayOf>(arrayOf(0, 1), arrayOf(1, 0), arrayOf(0, -1), arrayOf(-1, 0)) + + +/** + 记忆化深度优先搜索 + + 将递归的结果存储下来,这样每个子问题只需要计算一次。 + 从上面的分析中,我们知道在淳朴的深度优先搜索方法中有许多重复的计算。 + 一个优化途径是我们可以用一个集合来避免一次深度优先搜索中的重复访问。 + 该优化可以将一次深度优先搜索的时间复杂度优化到 O(mn)O(mn),总时间复杂度 O(m^2n^2)。 + + **/ + +fun longestIncreasingPath2(matrix: Array>): Int { + + if (matrix.size == 0) return 0 + //几列 + m = matrix.size + //几行 + n = matrix[0].size + //最长路径 + var ans = 0 + + val cache = Array(m) { IntArray(n) } + for (i in 0 until m) { + for (j in 0 until n) { + //每个顶点做四个方向的深度优先搜索 + ans = Math.max(ans, dfs2(matrix, i, j,cache)); + } + } + + return ans +} + +/** + * 记忆 深度优先搜索 + */ +fun dfs2( + matrix: Array>, + i: Int, + j: Int, + cache: Array +): Int { + //从缓存获取结果 + if (cache[i][j] != 0) return cache[i][j] + + //定义本次记录的路径长度 + var ans = 0 + //四个方向相当于图的四个分支,每个方向做深度优先搜索, + for (d in dirs) { + val x = i + d[0]//下一个元素行坐标 + val y = j + d[1]//下一个元素列坐标 + /** + * 校验下标合法范围 + */ + if (0 <= x && x < m && 0 <= y && y < n ){ + //表示递增的 + if( matrix[x][y] > matrix[i][j]){ + //每次查找如果比当前的大就替换 + //缓存每个节点的最长路径 + cache[i][j] = Math.max(ans, dfs2(matrix, x, y,cache)) + } + } + } + //这里+1表示到达当前一步 + return ++cache[i][j] +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode346.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode346.java new file mode 100644 index 0000000..a34586a --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode346.java @@ -0,0 +1,74 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Queue; + +/** + * 346. 数据流中的移动平均值 + * + * 给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算其所有整数的移动平均值。 + * + * 示例: + * + * MovingAverage m = new MovingAverage(3); + * m.next(1) = 1 + * m.next(10) = (1 + 10) / 2 + * m.next(3) = (1 + 10 + 3) / 3 + * m.next(5) = (10 + 3 + 5) / 3 + * + */ +public class LeetCode346 { + + public static void main(String[] args) { + System.out.println("----start----"); + + LeetCode346 leetCode346 = new LeetCode346(3); + System.out.println(leetCode346.next(1)); + System.out.println(leetCode346.next(10)); + System.out.println(leetCode346.next(3)); + System.out.println(leetCode346.next(5)); + } + + Deque dqueues = new ArrayDeque(); + + int maxSize = 0; + + int queueSum = 0; + + /** + * Initialize your data structure here. + */ + public LeetCode346(int size) { + this.maxSize = size; + } + + + public double next(int val) { + + //如果size小于maxSize 直接添加 + + //返回队列和除以真实size + + int removeValue = 0; + if (dqueues.size() >= maxSize) { + removeValue = dqueues.removeFirst(); + } + dqueues.add(val); + + if (queueSum == 0) { + for (Integer dqueue : dqueues) { + queueSum += dqueue; + } + } else { + //减去移除的,添加新的 + queueSum = queueSum - removeValue + val; + } + + return queueSum*1.0 / dqueues.size(); + } + +} + + + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode347.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode347.kt new file mode 100644 index 0000000..394ff8a --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode347.kt @@ -0,0 +1,82 @@ +package com.wangpos.datastructure.leetcode + + +import android.os.Build +import android.support.annotation.RequiresApi +import java.util.* + +/** + * + * 优先队列 + * + * 优先队列其实是一个二叉堆的结构,Binary Heap,他利用数组的结构来实现二叉树 + * + * 换句话说优先队列 本质是一个数组,数组里面每一个元素可能是其他元素父节点,也可能是期他元素子节点,并且每个父节点只能有2个子节点 + * + * 性质 + * 数组里面array[0] 是优先级最好的 + * 对于array[i]而言 + * + * 父节点是 (i-1)/2 + * 左孩子 2i + 1 + * 右孩子 2i + 2 + * + * 数组每个元素的优先级都高于孩子 + * + * + * 误区 每往堆里放入数据,都需要进行向上赛选,时间复杂度是nlog(n) 这是错误的,实际上再求极限时,时间复杂度是O(n) + * + * + * LeetCode 347 给一个非空数组,返回一个出现频率k高的元素 + * + * java的优先队列默认为小顶堆 + */ +@RequiresApi(Build.VERSION_CODES.N) +fun main() { + + val nums = arrayOf(1, 1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6) + val nums2 = arrayOf(1, 1, 1, 2, 2, 3, 4, 5) + + println("给定数组:${Arrays.toString(nums)}") + + val resultArrays = findPriorityData(nums, 2) + + println("优先级最高的 2 个元素:${Arrays.toString(resultArrays)}") +} + + +@RequiresApi(Build.VERSION_CODES.N) +fun findPriorityData(nums: Array, index: Int): IntArray { + + val map = mutableMapOf() + countPriority(nums, map) + val comparator = + Comparator> { entry1: Map.Entry, entry2: Map.Entry -> + //java 优先队列默认是小顶堆,所以这里通过 entry2 - entry1 的优先级得到一个大顶堆 + entry2.value - entry1.value + } + val priorityQueue = PriorityQueue>(comparator) + map.forEach { + priorityQueue.add(it) + } + val resultList = mutableListOf() + + for (k in 0 until index) { + resultList.add(priorityQueue.poll().key) + } + return resultList.toIntArray() +} + +private fun countPriority( + nums: Array, + map: MutableMap +) { + for (i in nums.indices) { + if (map.containsKey(nums[i])) { + val increaseNumber = map[nums[i]]!!.plus(1) + map[nums[i]] = increaseNumber + } else { + map[nums[i]] = 1 + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode382.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode382.kt new file mode 100644 index 0000000..fc3666e --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode382.kt @@ -0,0 +1,41 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + * 给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。 + +进阶: +如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现? + + */ +fun main() { + val head = ListNode(1) + head.next = ListNode(2) + head.next!!.next = ListNode(3) + + val solution = Solution382(head) + + println("结果:${solution.random}") +} + + +internal class Solution382(private val head: ListNode) { + + val random: Int + get() { + var res = head.`val` + var no = head.next + var i = 2 + val random = Random() + while (no != null) { + if (random.nextInt(i) === 0) { + res = no.`val` + } + i++ + no = no.next + } + return res + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode398.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode398.kt new file mode 100644 index 0000000..2dec387 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode398.kt @@ -0,0 +1,41 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + + +/** + * 给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。 + */ +fun main() { + + val array = arrayOf(1,2,3,4,4,4) + val solution398 = Solution398(array.toIntArray()) + + println("结果:${solution398.pick(4)}") +} + +internal class Solution398(nums: IntArray) { + var mp = mutableMapOf>() + + init { + for (i in nums.indices) { + if (mp.containsKey(nums[i]) === false) { + val list = ArrayList() + list.add(i) + mp[nums[i]] = list + } else { + mp[nums[i]]?.add(i) + } + } + + } + + fun pick(target: Int): Int { + val r = Random() + val index = mp[target]!!.size + val random = r.nextInt(index) + println(random) + return mp[target]!!.get(random) + } +} + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode4.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode4.kt new file mode 100644 index 0000000..bbc64c1 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode4.kt @@ -0,0 +1,152 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + * 4. 寻找两个有序数组的中位数 + * + * 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。 + +请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。 + +你可以假设 nums1 和 nums2 不会同时为空。 + + */ +fun main() { + + val num1 = arrayOf(1, 3, 5, 7, 9, 10).toIntArray() + val num2 = arrayOf(2, 4, 6, 8).toIntArray() +// val middleNumber = findMedianSortedArrays(num1, num2) + val middleNumber2 = findMedianSortedArrays2(num1,num2) + println("中位数 ${middleNumber2}") +} + +/** + * 暴力方法 合并再求解 时间复杂度是 O(m+n) + * 空间复杂度O(m+n) + */ +fun findMedianSortedArrays(nums1: Array, nums2: Array): Double { + + val m = nums1.size + val n = nums2.size + val nums = IntArray(m + n) + + //合并 + var count = 0 + var i = 0 + var j = 0 + while (count < (m + n)) { + if (i == m) { + while (j < n) { + nums[count++] = nums2[j++] + } + break + } + + if (j == n) { + while (i < m) { + nums[count++] = nums1[i++] + } + break + } + + //这里会存在i 或j 有一个先放完的情况,所以前面要做判断 + if (nums1[i] < nums2[j]) {//比较小的放数组前面,放完移动标志位进行下一个 + nums[count++] = nums1[i++] + } else { + nums[count++] = nums2[j++] + } + } + + println("合并后的数组${Arrays.toString(nums)}") + //获取中位数 + if (count % 2 == 0) { + //偶数 + return (nums[count / 2 - 1] + nums[count / 2]) / 2.0 + } else { + //奇数 + return nums[count / 2].toDouble() + } +} + + +fun findMedianSortedArrays2(nums1: IntArray, nums2: IntArray): Double { + val n = nums1.size + val m = nums2.size + val left = (n + m + 1) / 2 //如果是奇数,left位置 恰好是这个中位数 + val right = (n + m + 2) / 2 //偶数,需要获得(n+m)/2 和(n+m)/2两个数的平均值 + + //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。 +// return getKth(nums1, 0, n - 1, nums2, 0, m - 1, left).toDouble() + return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth( + nums1, + 0, + n - 1, + nums2, + 0, + m - 1, + right + )) * 0.5 +} + +/** + * 寻找第k个小的 + */ +fun getKth( + nums1: IntArray, + start1: Int, + end1: Int, + nums2: IntArray, + start2: Int, + end2: Int, + k: Int +): Int { + val len1 = end1 - start1 + 1 + val len2 = end2 - start2 + 1 + + // 如果len1大于len2 就对调一下,保证len1小于len2 + 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]) + + //如果k/2小于剩下的长度,就取最小值,否者下面会数组越界,这是一个新的start + + //这里注意为什么是k/2 ,而不是k, 如果是k 相当于我们再比较数组中最大值,然后排除掉,下一次还是比较数组最大值,这样效率很低 + //所以每次排除一半数量k,然后拿k/2和另两个数组长度比,找最小值作为比较的位置,然后舍去之前的位置 + val i = start1 + Math.min(len1, k / 2) - 1 + + val j = start2 + Math.min(len2, k / 2) - 1 + + if (nums1[i] > nums2[j]) { + //排除前j个 + return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1)); + } else { + //排除前i个 + return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1)); + + } + +} +// +//private 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/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode444.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode444.java new file mode 100644 index 0000000..fedf2ac --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode444.java @@ -0,0 +1,110 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * 验证原始的序列 org 是否可以从序列集 seqs 中唯一地重建。序列 org 是 1 到 n 整数的排列, + * + * 其中 1 ≤ n ≤ 10^4。重建是指在序列集 seqs 中构建最短的公共超序列。(即使得所有  seqs 中的序列都是该最短序列的子序列)。确定是否只可以从 seqs 重建唯一的序列,且该序列就是 org + */ +public class LeetCode444 { + + public boolean sequenceReconstruction(int[] org, List> seqs) { + int n = org.length; + + if (n == 0 || seqs.size() == 0) { + return false; + } + + // 考虑seqs里头元素为空列表的情况, 已经数字超过n或者<=0的情况 + Set numSet = new HashSet<>(); + for (List list : seqs) { + for (Integer num: list) { + if (num <= 0 || num > n) { + return false; + } + numSet.add(num); + } + } + + if (numSet.size() < n) { + return false; + } + + ArrayList[] adj = new ArrayList[n + 1]; + + for (int i = 1; i <= n; i++) { + adj[i] = new ArrayList<>(); + } + + createTable(seqs, adj); + + // 计算每个节点的入度 + int[] inDegree = new int[n + 1]; + + for (int i = 1; i <= n; i++) { + for (int j = 0; j < adj[i].size(); j++) { + int w = adj[i].get(j); + inDegree[w]++; + } + } + + // 计算入度为0的节点,添加到队列中 + LinkedList queue = new LinkedList<>(); + for (int i = 1; i <= n; i++) { + if (inDegree[i] == 0) { + queue.addLast(i); + } + } + + // 入度为0的节点有多个,就会产生多种序列,或者没有入度为0的,不满足题目要求 + if (queue.size() != 1) { + return false; + } + + int index = 0; + while (!queue.isEmpty()) { + int num = queue.removeFirst(); + if (org[index] != num) { + return false; + } + index++; + + // 删除当前节点后,所有当前节点的下一个节点的入度为0的个数,超过1则说明序列不唯一 + int nextZeroInDegreeCount = 0; + for (int j = 0; j < adj[num].size(); j++) { + int w = adj[num].get(j); + //遍历邻接表中相邻边的入度都减1 + inDegree[w]--; + if (inDegree[w] == 0) { + nextZeroInDegreeCount++; + //去掉一个结点后,如果入度为0的节点增加了不止1个,说明有多处一个分支,就直接返回false + if (nextZeroInDegreeCount > 1) { + return false; + } + + queue.addLast(w); + } + } + } + + return index == n; + } + + private void createTable(List> seqs, ArrayList[] adj) { + // 构建邻接表 + for (int i = 0; i < seqs.size(); i++) { + List pair = seqs.get(i); + + for (int j = 0; j < pair.size() - 1; j++) { + //数组中链表同样初始化 + adj[pair.get(j)].add(pair.get(j+1)); + } + } + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode464.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode464.java new file mode 100644 index 0000000..b994736 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode464.java @@ -0,0 +1,118 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +public class LeetCode464 { + + public static void main(String args[]) { + Solution solution = new Solution(); + + System.out.println(solution.canIWin(10, 11)); + +// System.out.println(solution.canIWin(10,12)); + + } + + static class Solution { + + public Set selects = new HashSet(); + + /** + * 1 A -1 B + */ + public int currentSelectPerson = 1; + + public int sum = 0; + + public boolean canIWin(int maxChoosableInteger, int desiredTotal) { + //12 + // 1 9 10 + // 选2 + + //Set集合初始化 + + + //最大和小于目标和返回false + int sumTemp = (1 + maxChoosableInteger) * maxChoosableInteger / 2; + if (sumTemp < desiredTotal) return false; + + + //有序链表 + LinkedList numPools = new LinkedList<>(); + +// for (int i = 1; i <= maxChoosableInteger; i++) { +// numPools.add(i); +// } + + + return canWinCalculate(1, maxChoosableInteger, desiredTotal, new HashMap()); + + } + + /** + * @param i 当前选的值 + * @param maxChoosableInteger + * @param desiredTotal + * @param integerBooleanHashMap + * @return + */ + private boolean canWinCalculate(int i, int maxChoosableInteger, int desiredTotal, HashMap integerBooleanHashMap) { + + for (i = maxChoosableInteger; i > 0; i--) { + selects.clear(); + selects.add(i); + currentSelectPerson = 1; + sum = i; + boolean result = canWinUnit(i, maxChoosableInteger, desiredTotal, integerBooleanHashMap); + if (result) { +// integerBooleanHashMap.put(i, result); + return result; + } + } + + return false; + } + + private boolean canWinUnit(int i, int maxChoosableInteger, int desiredTotal, HashMap integerBooleanHashMap) { + +// if (integerBooleanHashMap.containsKey(i)) { +// return integerBooleanHashMap.get(i); +// } + + boolean result = false; + int prevSum = sum; + +// Set filters = new HashSet(); + for (i = maxChoosableInteger; i > 0; i--) { + if (selects.contains(i)) continue; + selects.add(i); + sum = sum + i; + currentSelectPerson = -currentSelectPerson;//取相反数 + + if (sum >= desiredTotal) { + if (currentSelectPerson == 1) { + result = true; + } + } else { + result = canWinUnit(i, maxChoosableInteger, desiredTotal, integerBooleanHashMap); + } + //底下返回false 继续,返回true 停止 + if (result) { + break; + } else { + //恢复到之前状态 + sum = prevSum; + currentSelectPerson = -currentSelectPerson; + selects.remove(i); + } + } + + return result; + + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode478.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode478.kt new file mode 100644 index 0000000..11f900b --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode478.kt @@ -0,0 +1,24 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + + +fun main() { + val obj = LeetCode478(10.0,0.0,0.0) + + println("结果:${Arrays.toString(obj.randPoint())}") +} +internal class LeetCode478(var rad: Double, var xc: Double, var yc: Double) { + + fun randPoint(): DoubleArray { + val x0 = xc - rad + val y0 = yc - rad + + while (true) { + val xg = x0 + Math.random() * rad * 2.0 + val yg = y0 + Math.random() * rad * 2.0 + if (Math.sqrt(Math.pow(xg - xc, 2.0) + Math.pow(yg - yc, 2.0)) <= rad) + return doubleArrayOf(xg, yg) + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode493.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode493.kt new file mode 100644 index 0000000..e0dbf42 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode493.kt @@ -0,0 +1,82 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +/** + * 给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。 + * + * 使用归并排序可以求解 + * + * 每次二分后 到最后先比对两个集合是否存在翻转对,然后在进行排序,在每个小的集合中逐渐求逆序对, + * + * 如果因为两个排好序的数组逆序对比较好求,比如A数组中都是递增的,如果其中第i 大于B中的2被,则第i+1 到后面的都是翻转对,也就是size-i个 + * + * + * + */ +fun main() { + val leetCode = LeetCode493() + val leftArray = intArrayOf(3, 4, 5) + val rightArray = intArrayOf(1, 2, 3) + val counts = IntArray(1) +// leetCode.countReverse(leftArray,rightArray,counts) + + val nums = intArrayOf(2147483647, 2147483647, 2147483647, 2147483647, 2147483647, 2147483647) + + val numsSize = intArrayOf(2147483647,2147483647,-2147483647,-2147483647,-2147483647,2147483647) + val sortArray = leetCode.mergeSort(nums, 0, nums.size - 1, counts) + println(Arrays.toString(sortArray)) + println(counts[0]) +} + +class LeetCode493 { + + fun mergeSort(nums: IntArray, l: Int, h: Int, counts: IntArray): IntArray { + if (l == h) + return intArrayOf(nums[l]) + + val mid = l + (h - l) / 2 + val leftArr = mergeSort(nums, l, mid, counts) //左有序数组 + val rightArr = mergeSort(nums, mid + 1, h, counts) //右有序数组 + val newNum = IntArray(leftArr.size + rightArr.size) //新有序数组 + + countReverse(leftArr, rightArr, counts) + + var m = 0 + var i = 0 + var j = 0 + /** + * 两个数组 前一半比后一半小 + */ + while (i < leftArr.size && j < rightArr.size) { + newNum[m++] = if (leftArr[i] < rightArr[j]) leftArr[i++] else rightArr[j++] + } + //左边剩余 + while (i < leftArr.size) + newNum[m++] = leftArr[i++] + //右边剩余 + while (j < rightArr.size) + newNum[m++] = rightArr[j++] + + + return newNum + } + + fun countReverse(leftArr: IntArray, rightArr: IntArray, counts: IntArray) { + val leftArrSize = leftArr.size + for (i in rightArr.indices) { +// println("$i") + for (j in leftArr.indices) { +// println("j=$j") + val rightValue = rightArr[i].toLong() + val dRightValue = 2 * rightValue + if (leftArr[j] > dRightValue) { + counts[0] += (leftArrSize - j) +// println("a=$j ${counts[0]}") + break + } + } + } + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode5.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode5.kt new file mode 100644 index 0000000..c705234 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode5.kt @@ -0,0 +1,71 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +fun main() { + + val s = "abcdssbasdsdf" + + val resultString = findLongest(s) + + println("最大回文子串 ${resultString}") +} + +/** + * +1 0 0 0 0 0 0 1 0 0 0 0 0 +0 1 0 0 0 0 1 0 0 0 0 0 0 +0 0 1 0 0 0 0 0 0 0 0 0 0 +0 0 0 1 0 0 0 0 0 1 0 1 0 +0 0 0 0 1 1 0 0 1 0 1 0 0 +0 0 0 0 1 1 0 0 1 0 1 0 0 +0 1 0 0 0 0 1 0 0 0 0 0 0 +1 0 0 0 0 0 0 1 0 0 0 0 0 +0 0 0 0 1 1 0 0 1 0 1 0 0 +0 0 0 1 0 0 0 0 0 1 0 1 0 +0 0 0 0 1 1 0 0 1 0 1 0 0 +0 0 0 1 0 0 0 0 0 1 0 1 0 +0 0 0 0 0 0 0 0 0 0 0 0 1 +3 11 +最大回文子串 dssbasdsd + */ +fun findLongest(s: String): String { + + val size = s.length + + val array = Array(size) { IntArray(size) } + + var maxLength = 0 + var maxStart = 0 + var maxEnd = 0 + for (i in 0 until s.length) { + for (j in 0 until s.length) { + if (s[i] == s[j]) { + array[i][j] = 1 + if (i != j) { + if ((j - i) > maxLength) { + maxLength = (j - i) + maxStart = i + maxEnd = j + } + } + } + + } + } + + for (i in 0 until s.length) { + for (j in 0 until s.length) { + print(array[i][j]) + print(" ") + } + println() + } + + println("${maxStart} ${maxEnd}") + var resultString = "" + for (i in maxStart..maxEnd) { + resultString += s[i] + } + return resultString +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode582.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode582.java new file mode 100644 index 0000000..c010fb9 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode582.java @@ -0,0 +1,43 @@ +package com.wangpos.datastructure.leetcode; + +import android.os.Build; +import android.support.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class LeetCode582 { + class Node { + int val; + List children = new ArrayList<>(); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public List killProcess(List pid, List ppid, int kill) { + HashMap < Integer, List < Integer >> map = new HashMap < > (); + for (int i = 0; i < ppid.size(); i++) { + if (ppid.get(i) > 0) { + + List < Integer > l = map.getOrDefault(ppid.get(i), new ArrayList < Integer > ()); + l.add(pid.get(i)); + map.put(ppid.get(i), l); + } + } + Queue< Integer > queue = new LinkedList< >(); + List < Integer > l = new ArrayList < > (); + queue.add(kill); + while (!queue.isEmpty()) { + int r = queue.remove(); + l.add(r); + if (map.containsKey(r)) + for (int id: map.get(r)) + queue.add(id); + } + return l; + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode6.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode6.kt new file mode 100644 index 0000000..709e818 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode6.kt @@ -0,0 +1,98 @@ +package com.wangpos.datastructure.leetcode + +import android.R.string + + +fun main() { + + val testCase = "LEETCODEISHIRING" + + + val result = convert(testCase, 3) + +// convert(testCase, 10) +// convert(testCase, 11) +// convert(testCase, 12) +// convert(testCase, 13) +// convert(testCase, 14) +// convert(testCase, 15) +// convert(testCase, 16) + println("结果:${result}") +} + + +fun convert(s: String, numRows: Int): String { + + val size = s.length + + if (numRows == 1 || s.length <2) { + return s + } + + //有几个循环单位 + var d = (size - numRows) / (2 * numRows - 2) + + val yushu = (size - numRows) % (2 * numRows - 2) + + + /** + * 每一列 + * + */ + + var result = "" + +// println(d) + var index = 0 + /** + * 判断余数是否满足顶层 + */ + if (yushu >= (2 * numRows - 2) / 2) { + d++ + } + // 层数 + var b = 0 + while (b < numRows) { + var index = 0 + while (index <= d) { + if (b == 0) { + result += s[(numRows - 1) * 2 * index] + } else { + if (index == 0) { + println("2${index} ${d} ${b}") + if(((numRows - 1) * 2 * index + b) Integer.MAX_VALUE) { +// return 0 +// } +// +// if (x < 0) { +// s = "-" + s +// } +// return s.toInt() +// +//} + +fun reverse(x: Int): Int { + var x = x + var ans = 0 + while (x != 0) { + if (ans * 10 / 10 != ans) { + ans = 0 + break + } + ans = ans * 10 + x % 10 + x = x / 10 + } + return ans +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode739.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode739.kt new file mode 100644 index 0000000..3aa65b3 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode739.kt @@ -0,0 +1,63 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* +import kotlin.collections.ArrayList + +/** + * LeetCode 第 739 题:根据每日气温列表,请重新生成一个列表, + * 对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。 + * + * 示例:给定一个数组 T 代表了未来几天里每天的温度值,要求返回一个新的数组 D,D 中的每个元素表示需要经过多少天才能等来温度的升高。 +给定 T:[23, 25, 21, 19, 22, 26, 23] +返回 D: [ 1, 4, 2, 1, 1, 0, 0] + +第一个温度值是 23 摄氏度,它要经过 1 天才能等到温度的升高,也就是在第二天的时候,温度升高到 25 摄氏度,所以对应的结果是 1 +。接下来,从 25 度到下一次温度的升高需要等待 4 天的时间,那时温度会变为 26 度。 + + * + * + * 利用堆栈,还可以解决如下常见问题: +求解算术表达式的结果(LeetCode 224、227、772、770) +求解直方图里最大的矩形区域(LeetCode 84) + + */ +fun main() { + + //最直观的做法就是针对每个温度值向后进行依次搜索,找到比当前温度更高的值,这样的计算复杂度就是 O(n2)。 + + //但是中间有很多重复计算,所以可以使用栈来实现,该方法只需要对数组进行一次遍历,每个元素最多被压入和弹出堆栈一次,算法复杂度是 O(n) + + + val temperature = intArrayOf(23, 25, 21, 19, 22, 26, 23) + + val D = getUpList(temperature) + + println(Arrays.toString(D)) + +} + +fun getUpList(temperature: IntArray): IntArray { + + //存储温度 + val stack = Stack() + + val D = IntArray(temperature.size) + + for (i in temperature.indices) { + val it = temperature[i] + while (!stack.isEmpty()) { + if (temperature[stack.peek()] < it) { + D[stack.peek()] = i - stack.peek() + stack.pop() + }else{ + break + } + } +// stack.push(it) + //栈里面存的是数组下标,如果存值,再计算的时候就不知道是第几个了 + stack.push(i) + + } + + return D +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode8.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode8.kt new file mode 100644 index 0000000..b75a405 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode8.kt @@ -0,0 +1,76 @@ +package com.wangpos.datastructure.leetcode + +fun main() { + + val data = "9223372036854775808" + + val intNum = stringConvertInt(data) + + println("结果;${intNum}") +} + +fun stringConvertInt(str: String): Int { + val str = str.trim() + if (str.length == 0) { + return 0 + } + val isSmallZero = str[0] == '-' + var num: Long = 0 + for (k in 0 until str.length) { + val it = str[k] + + if (k == 0 && it !in '0'..'9') { + if(it !='-' &&it !='+') { + break + } + } + if(str.length>1) { + if ((str[0] == '-'||str[0] == '+') && str[1] !in '0'..'9') { + break + } + + if (it == '0' && str[1] !in '0'..'9') { + break + } + + } + if (it in '0'..'9') { + if(num>Integer.MAX_VALUE){ + break + } + if (num == 0L) { + num = charToInt(it).toLong() + } else { + num = num * 10 + charToInt(it) + } + }else{ + if(k==0 && it!='-'&&it!='+'){ + break + } + if(k!=0){ + break + } + } + } + if (num > Integer.MAX_VALUE) { + if(isSmallZero){ + num = Integer.MAX_VALUE.toLong()+1L + }else{ + num = Integer.MAX_VALUE.toLong() + } + + } + var result = 0 + if (isSmallZero) { + result = -num.toInt() + }else{ + result = num.toInt() + } + return result +} + +private fun charToInt(it: Char): Int { + val result = it.toString().toInt() + println(result) + return result +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode933.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode933.java new file mode 100644 index 0000000..674d08d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCode933.java @@ -0,0 +1,22 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * 933. 最近的请求次数 + */ +public class LeetCode933 { + + Queue q; + public LeetCode933() { + q = new LinkedList(); + } + + public int ping(int t) { + q.add(t); + while (q.peek() < t - 3000) + q.poll(); + return q.size(); + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCodeTreeArray.java b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCodeTreeArray.java new file mode 100644 index 0000000..cab1482 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/LeetCodeTreeArray.java @@ -0,0 +1,127 @@ +package com.wangpos.datastructure.leetcode; + + +/** + * 树状数组 + * 注意:和“堆”一样,“树状数组”的 00 号索引不放置元素,从 11 号索引开始使用。从上图可以观察到, + * 与数组 C 的某个结点有关的数组 A 的某些结点,它们的索引值之间有如下关系。 + * + * 初始化策略,通过update 方式,update 更新的是差值 + * + * parentIndex = i + lowbit(i); + * + + */ +public class LeetCodeTreeArray { + + int len = 0; + int[] tree; + + /** + * 初始化策略,通过update 方式,update 更新的是差值 + * @param nums + */ + LeetCodeTreeArray(int nums[]) { + this.len = nums.length + 1; + tree = new int[this.len + 1]; + for (int i = 1; i <= len; i++) { + update(i, nums[i-1]); + } + } + + public static void main(String[] args) { + System.out.println("Test"); + + int nums[]= {1,2,3,4,5}; + new LeetCodeTreeArray(nums); + } + + /** + * @param x 返回 2^k 每个tree的下标元素个数 + * @return + */ + public static int lowbit(int x) { + return x & (-x); + } + + /** + * 单点更新 + * + * @param i 原始数组索引 i + * @param delta 变化值 = 更新以后的值 - 原始值 + * + * parent(i)=i+lowbit(i) + * + * 先看 C[3] ,lowbit(3) = 1, 3 + lowbit(3) = 4 就是 C[3] 的父亲结点 C[4] 的索引值。 + * + * 再看 C[4] ,lowbit(4) = 4, 4 + lowbit(4) = 8 就是 C[4] 的父亲结点 C[8] 的索引值。 + + */ + public void update(int i, int delta) { + // 从下到上更新,注意,预处理数组,比原始数组的 len 大 1,故 预处理索引的最大值为 len + //i = 0 位置更新后面的都需要更新 C0 = 原始数据 +变化值 + while (i <= len) { + tree[i] += delta;//当前元素增加delta, tree[i]不管是一系列元素,还是一个元素都是增加一样的 + //获取parent 将parent 同样加上偏移量 + i = getParentIndex(i); + } + } + + private int getParentIndex(int i) { + i += lowbit(i); + return i; + } + + /** + * 查询前缀和 + * + * @param i 前缀的最大索引,即查询区间 [0, i] 的所有元素之和 + */ + public int query(int i) { + // 从右到左查询 + int sum = 0; + while (i > 0) { + sum += tree[i]; + i -= lowbit(i); + } + return sum; + } + +} + + +/** + * + * + * 使用 lowbit 实现“前缀和查询” + * lowbit 可以帮助我们计算前缀和由预处理数组的哪些元素表示。 + * + * 例:计算前 66 个元素的“前缀和”。 + * + * 由“图 3”可以看出前 66 个元素的“前缀和” = C[6] + C[4]。 + * + * 你可以验证一下: + * + * C[6] = C[5] + A[6]、C[5] = A[5],而 C[4] = A[1] + A[2] + A[3] + A[4] 。 + * + * 先看 C[6] ,lowbit(6) = 2, 6 - lowbit(6) = 4 正好是 C[6] 的上一个非叶子结点 C[4] 的索引值。 + * + * 可以这样理解: + * + * 数组 C 的结点的下标表示了数组 C 的元素来自多少个数组 A 的元素,也可以理解成高度,那么上一个非叶子结点,其实就是从右边向左边画一条水平线,遇到的墙的索引值。 + * + * 下面我们使用“前缀和(i)”表示前 i 个元素的“和”。 + * + * 例:计算前 55 个元素的“前缀和”。 + * + * 再看 C[5],lowbit(5) = 1, 5 - lowbit(6) = 4 正好是 C[5] 的上一个非叶子结点 C[4] 的索引值,故“前缀和(5)” = C[5] + C[4]。 + * + * 例:计算前 77 个元素的“前缀和”。 + * + * 再看 C[7],lowbit(7) = 1, 7 - lowbit(7) = 6 正好是 C[7] 的上一个非叶子结点 C[6] 的索引值,“前缀和(7)” = C[7] + C[6] + C[4]。 + * + * 例:计算前 88 个元素的“前缀和”。 + * + * 再看 C[8],lowbit(8) = 8, 8 - lowbit(8) = 0, 0 表示没有,从“图 3”也可以看出从右边向左边画一条水平线,不会遇到的墙,故“前缀和(8)” = C[8]。 + + */ \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/SnakeGame353.java b/app/src/main/java/com/wangpos/datastructure/leetcode/SnakeGame353.java new file mode 100644 index 0000000..285e606 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/SnakeGame353.java @@ -0,0 +1,217 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * 353. 贪吃蛇 + *

+ * 给定 width = 3, height = 2, 食物序列为 food = [[1,2],[0,1]]。 + *

+ * Snake snake = new Snake(width, height, food); + *

+ * 初始时,蛇的位置在 (0,0) 且第一个食物在 (1,2)。 + *

+ * 3,3,[[0,1],[0,2],[1,2],[2,2],[2,1],[2,0],[1,0]]],["R"],["R"],["D"],["D"],["L"],["L"],["U"],["U"],["R"],["R"],["D"],["D"],["L"],["L"],["U"],["R"],["U"],["L"],["D"]] + */ +public class SnakeGame353 { + + Deque snake = new ArrayDeque<>(); + + List bodys = new ArrayList<>(); + + Set snakePositionSet = new HashSet<>(); + int[][] food; + int width; + int height; + + int newfoodIndex = 0; + + int[] lastPosition = new int[2]; + + public static void main(String args[]) { +// int[][] food = new int[][]{new int[]{1, 2}, new int[]{0, 1}}; +// int[][] emptyFood = new int[0][0]; +// SnakeGame353 mainPanel = new SnakeGame353(3, 2, emptyFood); +// int[][] food = new int[][]{new int[]{2, 0}, new int[]{0, 0}, new int[]{0, 2}, new int[]{2, 2}}; + + int[][] food = new int[][]{new int[]{0, 1}, new int[]{0, 2}, new int[]{1, 2}, new int[]{2, 2}, new int[]{2, 1} + , new int[]{2, 0}, new int[]{1, 0} + }; + SnakeGame353 mainPanel = new SnakeGame353(3, 3, food); + + startMove(mainPanel, "R"); + startMove(mainPanel, "R"); + + startMove(mainPanel, "D"); + startMove(mainPanel, "D"); + startMove(mainPanel, "L"); + startMove(mainPanel, "L"); + + startMove(mainPanel, "U"); + startMove(mainPanel, "U"); +// +// + startMove(mainPanel, "R"); + startMove(mainPanel, "R"); +// +// startMove(mainPanel, "D"); +// startMove(mainPanel, "D"); +// startMove(mainPanel, "L"); +// startMove(mainPanel, "L"); +// startMove(mainPanel, "U"); +// startMove(mainPanel, "R"); +// +// startMove(mainPanel,"U"); +// startMove(mainPanel,"L"); +// startMove(mainPanel,"D"); + mainPanel.printSnake(); + } + + private static void startMove(SnakeGame353 leetCode353, String command) { + System.out.println("结果:" + leetCode353.move(command)); + } + + public SnakeGame353(int width, int height, int[][] food) { + this.width = width; + this.height = height; + this.food = food; + + int[] startPosition = getNewPosition(0, 0); +// snake.add(startPosition); + } + + private int[] getNewPosition(int x, int y) { + int[] position = new int[2]; + position[0] = x; + position[1] = y; + return position; + } + + /** + * Moves the snake. + * + * @param direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down + * @return The game's score after the move. Return -1 if game over. + * Game over when snake crosses the screen boundary or bites its body. + */ + public int move(String direction) { + switch (direction) { + case "U": + if (moveTop(lastPosition)) return -1; + break; + case "L": + if (moveLeft(lastPosition)) return -1; + break; + case "R": + if (moveRight(lastPosition)) return -1; + break; + case "D": + if (moveDown(lastPosition)) return -1; + break; + } + + return bodys.size(); + } + + private boolean moveDown(int[] lastPosition) { + int x = lastPosition[0]; + int y = lastPosition[1] + 1; + if (y < height) { + return move(x, y); + } else { + return true; + } + } + + private boolean checkCrashBody(int x, int y) { + //遍历蛇判断,可以通过HashSet保存蛇位置,然后进行O(1)判断 + for (int[] item : snake) { + if (item[0] == x && item[1] == y) { +// System.out.println("撞到自己身体 Game Over!"); +// System.exit(0); + return true; + } + } + return false; + } + + + private boolean moveRight(int[] lastPosition) { + int x = lastPosition[0] + 1; + int y = lastPosition[1]; + if (x < width) { + return move(x, y); + } else { + return true; + } + } + + private boolean moveLeft(int[] lastPosition) { + int x = lastPosition[0] - 1; + int y = lastPosition[1]; + if (x >= 0) { + return move(x, y); + } else { + return true; + } + } + + private boolean moveTop(int[] lastPosition) { + int x = lastPosition[0]; + int y = lastPosition[1] - 1; + if (y >= 0) { + return move(x, y); + } else { + return true; + } + } + + private boolean move(int x, int y) { + if (checkCrashBody(x, y)) { + return true; + } + int[] position = getNewPosition(x, y); + lastPosition = position; +// snake.addLast(position);//等于 addLast + checkFood(x, y, position); +// printSnake(); + return false; + } + + private void checkFood(int x, int y, int[] position) { + + if (newfoodIndex >= food.length) { + System.out.println("没有食物了"); + snake.addLast(position);//添加头部 + snake.removeFirst();//去掉尾部 空什么也不做 + return; + } + if (food[newfoodIndex][0] == y && food[newfoodIndex][1] == x) { + bodys.add(position); + newfoodIndex++; + snake.addLast(position); + } else { + if (!snake.isEmpty()) { + snake.addLast(position);//添加头部 + snake.removeFirst();//去掉尾部 空什么也不做 + } + } + + } + + private void printSnake() { + System.out.println("snake: "); + for (int[] ints : snake) { + System.out.println(Arrays.toString(ints)); + } + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/Solution1288.java b/app/src/main/java/com/wangpos/datastructure/leetcode/Solution1288.java new file mode 100644 index 0000000..3c03c24 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/Solution1288.java @@ -0,0 +1,59 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.Arrays; +import java.util.Comparator; + +class Solution1288 { + + /** + * 按区间开始排序如下,遍历的时候,只需要记录已遍历区间中的结束的最大值,只要后面的区间结束小于等于最大值, + * 就KO掉。区间开始一样只要保留一个即可。 + * + * 因为开始值我们肯定都是最小的 + * @param intervals + * @return + */ + public int removeCoveredIntervals(int[][] intervals) { + int max2 = 0; + int min1 = 0; + sort(intervals, new int[] {0,1}); + + //保留已遍历的最大的,只要自己2小了,就被删除 + int len = intervals.length; + for (int i = 0; i < intervals.length; i++) { + if(i-1 >= 0 && intervals[i-1][0] == intervals[i][0] && intervals[i-1][1] >=intervals[i][1]){ + len--; + continue; + } + else if(intervals[i][1] <= max2){ + len--; + continue; + }else if (intervals[i][1] > max2){ + max2 = intervals[i][1]; + } + } + return len; + } + + public static void sort(int[][] ob, final int[] order) { + Arrays.sort(ob, new Comparator() { + @Override + public int compare(Object o1, Object o2) { + int[] one = (int[]) o1; + int[] two = (int[]) o2; + for (int i = 0; i < order.length; i++) { + int k = order[i]; + if (one[k] > two[k]) { + return 1; + } else if (one[k] < two[k]) { + return -1; + } else { + continue; //如果按一条件比较结果相等,就使用第二个条件进行比较。 + } + } + return 0; + } + }); + } +} + diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/Test17.java b/app/src/main/java/com/wangpos/datastructure/leetcode/Test17.java new file mode 100644 index 0000000..589f078 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/Test17.java @@ -0,0 +1,64 @@ +package com.wangpos.datastructure.leetcode; + +public class Test17 { + + public static void main() { + int[] nums = new int[]{1, 2, 3, 4}; + int[] count = new Test17().decompressRLElist(nums); + System.out.println(count.length); + } + + public int[] decompressRLElist(int[] nums) { + + int count = 0; + int index = 0; + int[] resultArray; + for (int i = 0; i < nums.length; i++) { + // 0 1 2 3 4 5 + if ((i + 1) % 2 != 0) { + count += nums[i]; + } + } + resultArray = new int[count]; + + for (int i = 0; i < nums.length; i++) { + if ((i + 1) % 2 != 0) { + for (int j = 0; j < nums[i]; j++) { + resultArray[index] = nums[i + 1]; + index++; + } + } + + } + return resultArray; + + } + + public int[][] matrixBlockSum(int[][] mat, int K) { + + int[][] resultArray = new int[mat.length][mat[0].length]; + + for (int i = 0; i < mat.length; i++) { +//i - K <= r <= i + K, j - K <= c <= j + K +// for (int m = 0; m < mat[i].length; m++) { + int aa = i-K; + if(aa<0){ + aa=0; + } + int sum = 0; + for (int a = aa; a <= i + K && a < mat.length; a++) { + int bb = i-K; + if (bb<0){ + bb = 0; + } + for (int b=bb; b <= i + K && b < mat[i].length ; b++) { + sum += mat[a][b]; + } + } +// resultArray[i][m] = sum; +// } + } + + return resultArray; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/Test17.kt b/app/src/main/java/com/wangpos/datastructure/leetcode/Test17.kt new file mode 100644 index 0000000..0c44ae4 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/Test17.kt @@ -0,0 +1,20 @@ +package com.wangpos.datastructure.leetcode + +import java.util.* + +fun main() { + + val array = arrayOf(1,2,3,4).toIntArray() + val count = Test17().decompressRLElist(array) + +// println(Arrays.toString(count)) +// + val arrayMatrix = arrayOf(arrayOf(1,2,3).toIntArray(), + arrayOf(4,5,6).toIntArray(),arrayOf(7,8,9).toIntArray()) + + val resultArray = Test17().matrixBlockSum(arrayMatrix,1) + + resultArray.forEach { + println(Arrays.toString(it)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/Test18.java b/app/src/main/java/com/wangpos/datastructure/leetcode/Test18.java new file mode 100644 index 0000000..6eac650 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/Test18.java @@ -0,0 +1,32 @@ +package com.wangpos.datastructure.leetcode; + +import java.util.Arrays; + +public class Test18 { + public static void main(String[] args) { + System.out.println("Hello World"); + System.out.println(Arrays.toString(new Test18().getNoZeroIntegers(1010))); + } + + public int[] getNoZeroIntegers(int n) { + + for (int a = 1; a < n; a++) { + int c = n - a; + String as = a+""; + if(as.contains("0")){ + continue; + } + String s = c+""; + if(s.contains("0")){ + continue; + } + int result[] = new int[2]; + result[0] = a; + result[1] = c; + return result; + } + return new int[0]; + } + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode/readme.md b/app/src/main/java/com/wangpos/datastructure/leetcode/readme.md new file mode 100644 index 0000000..c4d57bd --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode/readme.md @@ -0,0 +1,43 @@ +LeetCode全部题型 + +- 数组(215) +- 动态规划(176) +- 数学(164) +- 字符串(153) +- 树(125) +- 哈希表(122) +- 深度优先搜索(115) +- 二分查找(82) +- 贪心算法(69) +- 广度优先搜索(63) +- 双指针(60) +- 栈(54) +- 回溯算法(53) +- 设计(43) +- 位运算(41) +- 排序(39) +- 图(38) +- 链表(37) +- 堆(34) +- 并查表(28) +- 滑动窗口(20) +- 分治算法(19) +- 字典树(17) +- 递归(15) +- 线段树(11) #307 #493 +- Ordered Map(10) #220 #352 #846 +- 队列(9) #346 #353 #582 #933 #622 #621 #641 +- 极小化极大(8) +- 树状数组(6) + - 又称 二叉索引树 又以其发明者命名为 Fenwick 树。其初衷是解决数据压缩里的累积频率的计算问题,现多用于高效计算数列的前缀和、区间和它可以以 O(logn) 的时间得到任意前缀和 + 并同时支持在 O(\log n)O(logn) 时间内支持动态单点值的修改。空间复杂度 O(n)O(n)。 + +- LineSweep(6) #1288 #1272 #1229 +- Random(6) +- 拓扑排序(6) #207 #210 #329 #444 #1203 +- 脑筋急转弯(5) #292 +- 几何(5) +- 二叉搜索树(2) #1038 #1214 +- Reject Sampling(2) #478 +- 蓄水池抽样(2) #382 #398 +- 记忆化(1) #329 \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/Cache.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/Cache.java new file mode 100644 index 0000000..3ceed2d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/Cache.java @@ -0,0 +1,14 @@ +package com.wangpos.datastructure.leetcode2; + +public class Cache { + + T element; + + public void setElement(T element){ + this.element = element; + } + + public T getElement(){ + return element; + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/CacheTest.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/CacheTest.java new file mode 100644 index 0000000..5cb8756 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/CacheTest.java @@ -0,0 +1,57 @@ +package com.wangpos.datastructure.leetcode2; + +import android.util.Log; + +import java.lang.reflect.Field; + +public class CacheTest { + + public static void main(String args[]) { + + Cache cache = new Cache(); + Class eclz = cache.getClass(); + System.out.println("diskCache class is:" + eclz.getName()); + + Field[] fs = eclz.getDeclaredFields(); + for (Field f : fs) { + System.out.println("Field name " + f.getName() + " type:" + f.getType().getName()); + } + + + Character a = 'a'; + Character b = '啊'; + Integer c = 5; + System.out.println(a.SIZE); + System.out.println(b.SIZE); + +// diskCache class is:com.wangpos.datastructure.leetcode2.Cache +// Field name element type:java.lang.Object + + Integer integer1 = new Integer(212); + Integer integer2 = new Integer(212); + + if (integer1 == integer2) { + System.out.println("true"); + }else{ + System.out.println("false"); + } + + String str = null; + + try { + str.toString(); + }catch (Exception e){ + System.out.println("程序异常"); + e.printStackTrace(); + } + + System.out.println("程序执行结束"); + } + + + static class A { + public static void printAContent() { + + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/DiskCache.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/DiskCache.java new file mode 100644 index 0000000..5a87972 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/DiskCache.java @@ -0,0 +1,4 @@ +package com.wangpos.datastructure.leetcode2; + +public class DiskCache extends Cache { +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode220.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode220.java new file mode 100644 index 0000000..9b7030c --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode220.java @@ -0,0 +1,132 @@ +package com.wangpos.datastructure.leetcode2; + +import com.wangpos.datastructure.leetcode.LeetCode464; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Consumer; + +public class LeetCode220 { + public static void main(String args[]) { + Solution solution = new Solution(); + + long nums[] = new long[]{1, 2, 3, 1}; + long nums1[] = new long[]{1, 0, 1, 1}; + long nums2[] = new long[]{1, 5, 9, 1, 5, 9}; + long nums3[] = new long[]{-1, 2147483647}; + int nums4[] = new int[]{2147483647, -2147483647}; +// System.out.println(solution.containsNearbyAlmostDuplicate(nums,3,0)); +// +// System.out.println(solution.containsNearbyAlmostDuplicate(nums1,1,2)); +// System.out.println(solution.containsNearbyAlmostDuplicate(nums2,2,3)); +// System.out.println(solution.containsNearbyAlmostDuplicate(nums3,1,2147483647)); + System.out.println(solution.containsNearbyAlmostDuplicate(nums4, 1, 2147483647)); + +// 报错的两种情况,因为虽然Number 是Float 父类,但是也推断不出来 List list44 是List父类 +// List list4 = new ArrayList(); +// List list44 = new ArrayList(); + + //所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的 + //为了让泛型用起来更舒服,Sun的大脑袋们就想出了的办法,来让”水果盘子“和”苹果盘子“之间发生关系。 +// +// List list11 = new ArrayList(); +// //Integer是Number的子类 +// List list22 = new ArrayList(); +// //Float也是Number的子类 +// List list33 = new ArrayList(); +// +// //上面只能遍历不能添加 获取Number 类型 +// Number a = list11.get(0); // right +// // Integer b = list22.get(0);// Error 只能取出Number 类型 +// // 适合用场景,限制集合的修改操作, 只能获取Number 类型的元素 +// +// +// +// List list = new ArrayList(); +// //Number是Float的父类 +// List list2 = new ArrayList(); +// list2.add(0.1f);//可以调价Float类型,相当于Float 可以复制给Number;子类可以复制给父类 +// Object obj = list2.get(0); +// //Object是Number的父类 +// List list3 = new ArrayList(); +// +// //上面只能添加Float类型,但读出不出来具体类型,只能是Object,限制了集合的使用 +// +//// PECS(Producer Extends Consumer Super)原则,已经很好理解了: +//// +//// 频繁往外读取内容的,适合用上界Extends。 +//// 经常往里插入的,适合用下界Super。 +// Collections.copy(new ArrayList(),new ArrayList()); +// Collections.copy(new ArrayList(),new ArrayList()); +// // Collections.copy(new ArrayList(),new ArrayList());//Error +//// Java中所有类的顶级父类是Object,可以认为Null是所有类的子类。 + + } + + static class Solution { +// public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) { +// Set records = new HashSet<>(); +// for (int i = 0; i < nums.length; i++) { +// int maxLength = i + k; +// if (maxLength >= nums.length) maxLength = nums.length-1; +// +// +// for (int j = i+1; j <= maxLength; j++) { +// //去除重复计算 +// if(records.contains(i+"_"+j)) continue; +// long a = nums[i]; +// long b = nums[j]; +// long result = a - b; +// if (Math.abs(result) <= t) { +// return true; +// } +// records.add(i+"_"+j); +// } +// } +// +// return false; +// } + + /** + * TreeSet 实现使用TreeMap ,TreeMap 实现是红黑树,默认自然顺序 + * TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。 + * 通过ceiling 和Floor 可以找到最贴近的元素 + * + * @param nums + * @param k + * @param t + * @return + */ + public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) { + //自然平衡二叉树,来表示滑动窗口,这个窗口最大size 不能大于k + TreeSet set = new TreeSet<>(); + for (int i = 0; i < nums.length; ++i) { + // Find the successor of current element + + // ceiling(E e) 方法返回在这个集合中大于或者等于给定元素的最小元素,如果不存在这样的元素,返回null. + // 集合中大于此元素的最小值,最小值 + Integer s = set.ceiling(nums[i]); + //s - nums[i]<=t 等式变换得到,集合中满足条件的最小值都不可以,那就其余都不行 + if (s != null && s <= nums[i] + t) return true; + + // 集合中小于此元素的最大值 + // Find the predecessor of current element + Integer g = set.floor(nums[i]); + if (g != null && nums[i] <= g + t) return true; + + //没找到添加到树中,并且会自然平衡 + set.add(nums[i]); + if (set.size() > k) { + //向前数第k个移除 + set.remove(nums[i - k]); + } + } + return false; + } + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode352.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode352.java new file mode 100644 index 0000000..3a917c5 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode352.java @@ -0,0 +1,140 @@ +package com.wangpos.datastructure.leetcode2; + +import java.util.TreeMap; + +public class LeetCode352 { + + + public static void main(String args[]) { + SummaryRanges summaryRanges = new SummaryRanges(); + summaryRanges.addNum(6); + summaryRanges.addNum(6); + summaryRanges.addNum(0); + summaryRanges.addNum(4); + summaryRanges.addNum(8); + summaryRanges.addNum(7); + summaryRanges.addNum(6); + summaryRanges.addNum(4); + summaryRanges.addNum(7); + summaryRanges.addNum(5); + + int[][] resultArray = summaryRanges.getIntervals(); + for (int[] ints : resultArray) { + System.out.print(ints[0]); + System.out.print(" - "); + System.out.println(ints[1]); + } + + } + + static class SummaryRanges { + + /** + * Initialize your data structure here. + */ + + TreeMap treeMap = new TreeMap<>(); + + public SummaryRanges() { + + } + + public void addNum(int data) { + Integer leftKey = treeMap.floorKey(data); +// System.out.println("左边挨着的Key" + leftKey); + Integer rightKey = treeMap.higherKey(data); +// System.out.println("右边边挨着的Key" + leftKey); + updateData(data, leftKey, rightKey); + + } + + private void updateData(Integer data, Integer leftKey, Integer rightKey) { + boolean leftIsUpdate = false; + boolean rightIsUpdate = false; + if (leftKey != null) { + leftIsUpdate = checkUpdateLeftData(data, leftKey); + } + + if (rightKey != null) { + rightIsUpdate = checkUpdateRightData(data, rightKey); + } + + //有更新去处理合并 + if (leftIsUpdate || rightIsUpdate) { + //前后都有值 + if (leftKey != null && rightKey != null) { + checkMerge(leftKey, rightKey); + } + } + + //都没更新就加入新的 + if (!leftIsUpdate && !rightIsUpdate) { + addNewData(data); + } + } + + private void checkMerge(Integer leftKey, Integer rightKey) { + Integer leftValue = treeMap.get(leftKey); + Integer rightValue = null; + //相同证明不存在需要合并的right + if (leftKey != leftValue) { + rightValue = treeMap.get(leftValue); + } + if (rightValue != null) { + merge(leftKey, leftValue, rightValue); + } + } + + private void addNewData(Integer data) { + treeMap.put(data, data); + } + + private void merge(Integer leftKey, Integer rightKey, Integer rightValue) { + treeMap.put(leftKey, rightValue); + treeMap.remove(rightKey); + } + + private boolean checkUpdateRightData(Integer data, Integer rightKey) { + Integer rightValue = treeMap.get(rightKey); +// System.out.println("右边挨着的值" + rightValue); + + if (data == rightKey) { + return true; + } else if ((data + 1) == rightKey) { + treeMap.put(data, rightValue); + treeMap.remove(rightKey); + return true; + } else { + return false; + } + + } + + private boolean checkUpdateLeftData(int data, Integer leftKey) { + Integer leftValue = treeMap.get(leftKey); +// System.out.println("左边挨着的值" + leftValue); + if (data <= leftValue) { + return true; + } else { + if (data == leftValue + 1) { + treeMap.put(leftKey, data); + return true; + } else { + return false; + } + } + } + + public int[][] getIntervals() { + + int arrays[][] = new int[treeMap.size()][2]; + int index = 0; + for (Integer integer : treeMap.keySet()) { + arrays[index][0] = integer; + arrays[index][1] = treeMap.get(integer); + index++; + } + return arrays; + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode699.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode699.java new file mode 100644 index 0000000..971a18d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode699.java @@ -0,0 +1,99 @@ +package com.wangpos.datastructure.leetcode2; + +import com.wangpos.datastructure.leetcode.Tree; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.TreeMap; + +public class LeetCode699 { + + public static void main(String args[]) { + Solution solution = new Solution(); + + int[][] positions = new int[][]{new int[]{9, 7}, new int[]{1, 9}, new int[]{3, 1}}; + List resultList = solution.fallingSquares(positions); + System.out.println(Arrays.toString(resultList.toArray())); + } + + static class Solution { + + //X轴每个点对应高度 + HashMap xAxisHeights = new HashMap<>(); + //每个点 + List postionHeights = new ArrayList<>(); + //当前最大高度 + Integer currentMaxHeight = 0; + + TreeMap lineRecords = new TreeMap<>(); + + public List fallingSquares(int[][] positions) { + for (int[] position : positions) { + addPosition(position); + } + return postionHeights; + } + + private void addPosition(int[] position) { + int length = position[0] + position[1]; + int increaseHeight = position[1]; + int coverMaxHeight = 0; + for (int start = position[0]; start <= length; start++) { + Integer oldHeight = xAxisHeights.get(start); + if (oldHeight != null) { + if (oldHeight > coverMaxHeight) { + coverMaxHeight = oldHeight; + } + } + } + int newMaxHeight = coverMaxHeight + increaseHeight; + //去掉两个端点,假设第二块恰好挨着第一块落下端点的高度应该是原先值 + + + Integer leftKey = lineRecords.floorKey(position[0]); + Integer leftValue = lineRecords.get(leftKey); + if (position[0] < leftValue) { + xAxisHeights.put(position[0], newMaxHeight); + } + + Integer rightKey = lineRecords.higherKey(length); + if (length != rightKey) { + xAxisHeights.put(length, newMaxHeight); + } + + + for (int start = position[0] + 1; start < length; start++) { + //判断开始和结束点是否落在边上,还是里面 + xAxisHeights.put(start, newMaxHeight); + } + if (newMaxHeight > currentMaxHeight) { + currentMaxHeight = newMaxHeight; + } + + //计算正方形以单位1的覆盖x 取出所有覆盖点,比较最大高度,最大高度的点值为加上新的点高度,其他点一并更新 + + //更新每个点的高度, + + // 并与当前最高比较如果大于更新currentMaxHeight + + //向posinoHeights记录当前时间点的最大高度 +// int l = position[0]; +// int r = length; +// if (position[0] < leftValue) { +// xAxisHeights.put(leftKey, length);//合并左边 +// l = leftKey; +// } else { +// if(length) +// } + + + lineRecords.put(position[0], position[0] + position[1]); + postionHeights.add(currentMaxHeight); + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode731.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode731.java new file mode 100644 index 0000000..e9fa5cb --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode731.java @@ -0,0 +1,86 @@ +package com.wangpos.datastructure.leetcode2; + +import java.util.TreeMap; + +public class LeetCode731 { + + TreeMap singleMap = new TreeMap<>(); + TreeMap doubleMap = new TreeMap<>(); + + public LeetCode731() { + + } + + public boolean book(int start, int end) { + //返回小于key最大的Key + Integer j = doubleMap.lowerKey(start);//出现两次区间中,起点小于start的 + //大于等于Key的最小Key + Integer k = doubleMap.ceilingKey(start);//出现两次区间中,起点大于等于start的 + if (j != null && doubleMap.get(j) > start) { + return false; + } + if (k != null && k < end) { + return false; + } + Integer a = singleMap.lowerKey(start); + + Integer b = singleMap.ceilingKey(start); + + //完全没有冲突 + //判断开始元素比他小的不存在 + /** + * + * left = a + * + * right = singleMap.get(a) + * + * 新插入的start end 如果不能存在重复 + * + * 则应该瞒住,start 不在这个集合中存在,或者如果存在,start应该大于这个区间的右区间也就是right + * + * 并且,end 不存在,或者小于这个区间left区间 + * + */ + if ((a == null || singleMap.get(a) < start) + && (b == null || b > end)) { + singleMap.put(start, end); + return true; + } + + if (a == null || singleMap.get(a) < start) { + } else { + //与前一个区间有重叠部分,将重叠部分塞入doubleMap,同时在singleMap中合并当前区间和前一个区间 + Integer i = singleMap.get(a); + //区间合并 + singleMap.put(a, Math.max(end, i)); + //存储重复区间 + addDouble(start, Math.min(end, i)); + start = a; + end = Math.max(end, i); + } + + //如果和后一个区间有冲突,冲突区域塞入doubleMap,同时在singleMap中合。 + //需要考虑当前准备插入的区间,同时包含了多个已有区间的情况,所以继续向后找 + //这里应该有优化点,直接查找后一个是比调用higherKey方法来得快的,没仔细想了 + while (b != null && b <= end) { + Integer i = singleMap.get(b); + addDouble(b, Math.min(end, i)); + singleMap.remove(b); + //这里start 如果前一区间重就需要重新合并 + singleMap.put(start, Math.max(end, i)); + end = Math.max(end, i); + //返回大于start最小值 + b = singleMap.higherKey(start); + } + + return true; + } + + public void addDouble(int start, int end) { + if (start >= end) { + return; + } + doubleMap.put(start, end); + } + +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode846.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode846.java new file mode 100644 index 0000000..8e7fe84 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/LeetCode846.java @@ -0,0 +1,73 @@ +package com.wangpos.datastructure.leetcode2; + +import android.annotation.TargetApi; +import android.os.Build; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class LeetCode846 { + + class Solution { + public boolean isNStraightHand(int[] hand, int W) { + + //排序 + Arrays.sort(hand); + int len = hand.length; + if (len % W != 0) { + return false; + } + + //将数组添加到列表中,方便移除 + List list = new ArrayList<>(); + for (int i = 0; i < len; i++) { + list.add(hand[i]); + } + + while (list.size() > 0) { + + Integer curVal = list.get(0); + for (int i = 0; i < W; i++) { + if (list.size() == 0) + return false; + if (!list.remove(curVal)) + return false; + curVal++; + } + } + + return true; + + } + + @TargetApi(Build.VERSION_CODES.N) + public boolean isNStraightHand2(int[] hand, int W){ + //通过TreeMap实现数组的排序,并记录元素出现的次数 + TreeMap count = new TreeMap(); + for (int card: hand) { + if (!count.containsKey(card)) + count.put(card, 1); + else + count.replace(card, count.get(card) + 1); + } + + while (count.size() > 0) { + //读取第一个数组 + int first = count.firstKey(); + for (int card = first; card < first + W; ++card) { + //不够直接返回fasle + if (!count.containsKey(card)) return false; + int c = count.get(card); + if (c == 1) count.remove(card); + else count.replace(card, c - 1); + } + } + + return true; + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/TestReentranlock.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/TestReentranlock.java new file mode 100644 index 0000000..67ec567 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/TestReentranlock.java @@ -0,0 +1,129 @@ +package com.wangpos.datastructure.leetcode2; + +import com.wangpos.datastructure.java.condition.BoundedBuffer; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class TestReentranlock { + + public static void main(String args[]) { + final BoundedBuffer bb = new BoundedBuffer(); + + Thread t1 = new Thread() { + @Override + public void run() { + super.run(); + try { + while (true) { + System.out.println("准备写入数据"); + bb.put(new Object()); + System.out.println("写入数据完成"); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + t1.start(); + + + Thread t3 = new Thread() { + @Override + public void run() { + super.run(); + try { + while (true) { + System.out.println("准备写入数据3"); + bb.put(new Object()); + System.out.println("写入数据完成3"); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + t3.start(); + + Thread t2 = new Thread() { + @Override + public void run() { + super.run(); + try { + sleep(2000); + System.out.println("准备读出数据......"); + bb.take(); + System.out.println("读出数据完成....."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + t2.start(); + } + + /** + * 准备写入数据 + * 队列未满 + * 准备唤醒读线程 + * 写入数据完成 + * 准备写入数据 + * 队列满了 + * 准备读出数据...... + * 准备唤醒写线程 + * 读出数据完成..... + * 队列满了1 + * 队列未满 + * 准备唤醒读线程 + * 写入数据完成 + * 准备写入数据 + * 队列满了 + */ + static class BoundedBuffer { + final Lock lock = new ReentrantLock();//锁对象 + final Condition notFull = lock.newCondition();//写线程条件 + final Condition notEmpty = lock.newCondition();//读线程条件 + + final Object[] items = new Object[1];//缓存队列 + int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/; + + public void put(Object x) throws InterruptedException { + lock.lock(); + try { + while (count == items.length) {//如果队列满了 + System.out.println("队列满了"); + notFull.await();//阻塞写线程 + //被唤醒后执行,及时多个限制再阻塞,拿到锁的也只有一个,所以只会唤醒一个 + System.out.println("队列满了1》》》》》》》》》》》》》》》》》》》"); + } + System.out.println("队列未满"); + items[putptr] = x;//赋值 + if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0 + ++count;//个数++ + System.out.println("准备唤醒读线程"); + notEmpty.signal();//唤醒读线程 + } finally { + lock.unlock(); + } + } + + public Object take() throws InterruptedException { + lock.lock(); + try { + while (count == 0) {//如果队列为空 + System.out.println("队列为空"); + notEmpty.await();//阻塞读线程 + } + Object x = items[takeptr];//取值 + if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0 + --count;//个数-- + System.out.println("准备唤醒写线程"); + notFull.signal();//唤醒写线程 + return x; + } finally { + lock.unlock(); + } + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/TestSort.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/TestSort.java new file mode 100644 index 0000000..9387f41 --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/TestSort.java @@ -0,0 +1,31 @@ +package com.wangpos.datastructure.leetcode2; + +public class TestSort { + + public static int[] mergeSort(int[] nums, int l, int h) { + if (l == h) + return new int[] { nums[l] }; + + int mid = l + (h - l) / 2; + int[] leftArr = mergeSort(nums, l, mid); //左有序数组 + int[] rightArr = mergeSort(nums, mid + 1, h); //右有序数组 + int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组 + + int m = 0, i = 0, j = 0; + while (i < leftArr.length && j < rightArr.length) { + newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++]; + } + while (i < leftArr.length) + newNum[m++] = leftArr[i++]; + while (j < rightArr.length) + newNum[m++] = rightArr[j++]; + return newNum; + } + public static void main(String[] args) { + int[] nums = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 10 }; + int[] newNums = mergeSort(nums, 0, nums.length - 1); + for (int x : newNums) { + System.out.println(x); + } + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/leetcode2/Trie.java b/app/src/main/java/com/wangpos/datastructure/leetcode2/Trie.java new file mode 100644 index 0000000..8b1909d --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/leetcode2/Trie.java @@ -0,0 +1,150 @@ +package com.wangpos.datastructure.leetcode2; + + +public class Trie { + private int SIZE = 26; + private TrieNode root;//字典树的根 + + Trie() //初始化字典树 + { + root = new TrieNode(); + } + + private class TrieNode //字典树节点 + { + private int num;//有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数 + private TrieNode[] son;//所有的儿子节点 + private boolean isEnd;//是不是最后一个节点 + private char val;//节点的值 + private boolean haveSon; + + TrieNode() { + num = 1; + son = new TrieNode[SIZE]; + isEnd = false; + haveSon = false; + } + } + + //建立字典树 + public void insert(String str) //在字典树中插入一个单词 + { + if (str == null || str.length() == 0) { + return; + } + TrieNode node = root; + char[] letters = str.toCharArray(); + for (int i = 0, len = str.length(); i < len; i++) { + int pos = letters[i] - 'a'; + if (node.son[pos] == null) { + node.haveSon = true; + node.son[pos] = new TrieNode(); + node.son[pos].val = letters[i]; + } else { + node.son[pos].num++; + } + node = node.son[pos]; + } + node.isEnd = true; + } + + //计算单词前缀的数量 + public int countPrefix(String prefix) { + if (prefix == null || prefix.length() == 0) { + return -1; + } + TrieNode node = root; + char[] letters = prefix.toCharArray(); + for (int i = 0, len = prefix.length(); i < len; i++) { + int pos = letters[i] - 'a'; + if (node.son[pos] == null) { + return 0; + } else { + node = node.son[pos]; + } + } + return node.num; + } + + //打印指定前缀的单词 + public String hasPrefix(String prefix) { + if (prefix == null || prefix.length() == 0) { + return null; + } + TrieNode node = root; + char[] letters = prefix.toCharArray(); + for (int i = 0, len = prefix.length(); i < len; i++) { + int pos = letters[i] - 'a'; + if (node.son[pos] == null) { + return null; + } else { + node = node.son[pos]; + } + } + preTraverse(node, prefix); + return null; + } + + // 遍历经过此节点的单词. + public void preTraverse(TrieNode node, String prefix) { + if (node.haveSon) { + for (TrieNode child : node.son) { + if (child != null) { + preTraverse(child, prefix + child.val); + } + } + return; + } + System.out.println(prefix); + } + + + //在字典树中查找一个完全匹配的单词. + public boolean has(String str) { + if (str == null || str.length() == 0) { + return false; + } + TrieNode node = root; + char[] letters = str.toCharArray(); + for (int i = 0, len = str.length(); i < len; i++) { + int pos = letters[i] - 'a'; + if (node.son[pos] != null) { + node = node.son[pos]; + } else { + return false; + } + } + return node.isEnd; + } + + //前序遍历字典树. + public void preTraverse(TrieNode node) { + if (node != null) { + System.out.print(node.val + "-"); + for (TrieNode child: node.son) { + preTraverse(child); + } + } + } + + public TrieNode getRoot() { + return this.root; + } + + public static void main(String[] args) { + Trie tree = new Trie(); + String[] strs = {"banana", "band", "bee", "absolute", "acm",}; + String[] prefix = {"ba", "b", "band", "abc",}; + for (String str: strs) { + tree.insert(str); + } + System.out.println(tree.has("abc")); + tree.preTraverse(tree.getRoot()); + System.out.println(); +// tree.printAllWords(); + for (String pre: prefix) { + int num = tree.countPrefix(pre); + System.out.println(pre + "" + num); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wangpos/datastructure/other/GetMinActivity.java b/app/src/main/java/com/wangpos/datastructure/other/GetMinActivity.java new file mode 100644 index 0000000..653d77e --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/other/GetMinActivity.java @@ -0,0 +1,65 @@ +package com.wangpos.datastructure.other; + +import com.wangpos.datastructure.core.BaseActivity; + +/** + * Created by qiyue on 2018/6/27. + */ + +public class GetMinActivity extends BaseActivity { + MyStack myStack = null; + @Override + protected void initData() { + + myStack = new MyStack(); + myStack.push(4); + myStack.push(3); + myStack.push(1); + myStack.push(2); + + + } + + @Override + protected String getTextData() { + return "{4,3,1,2}"; + } + + @Override + protected int getImageData() { + return 0; + } + + @Override + protected String getResultData() { + return ""+"min="+myStack.getMin(); + } + + @Override + protected String getTimeData() { + return null; + } + + @Override + protected String getSpaceTimeData() { + return null; + } + + @Override + protected String getWendingXingData() { + return null; + } + + @Override + protected String getSummaryData() { + return null; + } + + + private void getMin(){ + + } + + + +} diff --git a/app/src/main/java/com/wangpos/datastructure/other/MyStack.java b/app/src/main/java/com/wangpos/datastructure/other/MyStack.java new file mode 100644 index 0000000..d2b84bd --- /dev/null +++ b/app/src/main/java/com/wangpos/datastructure/other/MyStack.java @@ -0,0 +1,54 @@ +package com.wangpos.datastructure.other; + +import java.util.Stack; + +/** + * Created by qiyue on 2018/6/27. + */ + +public class MyStack { + + private Stack stackData; + + private Stack stackMin; + + public MyStack() { + this.stackData = new Stack(); + this.stackMin = new Stack(); + } + + public void push(int num) { + stackData.push(num); + if (!stackMin.isEmpty()) { + stackMin.push(num); + } + if (num < stackMin.pop()) { + stackMin.push(num); + } + stackData.push(num); + } + + public int pop() { + + int data = 0; + if(stackData.isEmpty()){ + throw new RuntimeException("集合为null"); + } + + data = stackData.pop(); + + if (data == getMin()){ + stackMin.pop(); + } + return data; + + } + + public int getMin() { + if(stackMin.isEmpty()){ + throw new RuntimeException("数据为null"); + } + return stackMin.peek(); + + } +} diff --git a/app/src/main/java/com/wangpos/datastructure/sort/MergeSortActivity.java b/app/src/main/java/com/wangpos/datastructure/sort/MergeSortActivity.java index da6502a..d128816 100644 --- a/app/src/main/java/com/wangpos/datastructure/sort/MergeSortActivity.java +++ b/app/src/main/java/com/wangpos/datastructure/sort/MergeSortActivity.java @@ -84,6 +84,23 @@ private static void Sort(int[] a, int left, int right) { } + /** + * + * 算法为三个方法 + * + * 归并排序是先拆分,按照2分或者3分等等,然后将最小不可分的 + * + * 然后需要一个辅助控件 + * 和辅助的cIndex = left 以便后面拷贝 + * 需要一个tmpIndex临时变量 + * 和rightIndex作为右边判断 + * + * + * @param a + * @param left + * @param mid + * @param right + */ private static void merge(int[] a, int left, int mid, int right) { int[] tmp = new int[a.length]; diff --git a/app/src/main/java/com/wangpos/datastructure/sort/QuickSortActivity.java b/app/src/main/java/com/wangpos/datastructure/sort/QuickSortActivity.java index 6e7e992..be0881f 100644 --- a/app/src/main/java/com/wangpos/datastructure/sort/QuickSortActivity.java +++ b/app/src/main/java/com/wangpos/datastructure/sort/QuickSortActivity.java @@ -137,6 +137,9 @@ private static int sortUnit(int[] array, int low, int high) return meetPosition; } + + + /**快速排序 *@paramarry *@return */ diff --git a/app/src/main/res/layout/activity_designv28.xml b/app/src/main/res/layout/activity_designv28.xml new file mode 100644 index 0000000..6050ad5 --- /dev/null +++ b/app/src/main/res/layout/activity_designv28.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/measure_layout.xml b/app/src/main/res/layout/measure_layout.xml new file mode 100644 index 0000000..f8592b8 --- /dev/null +++ b/app/src/main/res/layout/measure_layout.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/wangpos/datastructure/java/mylist/TestCJList.java b/app/src/test/java/com/wangpos/datastructure/java/mylist/TestCJList.java new file mode 100644 index 0000000..1cb74eb --- /dev/null +++ b/app/src/test/java/com/wangpos/datastructure/java/mylist/TestCJList.java @@ -0,0 +1,53 @@ +package com.wangpos.datastructure.java.mylist; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * Created by qiyue on 2018/6/20. + */ + +public class TestCJList { + + + @Test + public void testArrayList(){ + int a[] = {1,2}; + + int b[] = Arrays.copyOf(a,6); + + System.out.println(Arrays.toString(b)); + } + + @Test + public void testCJList(){ + CJArrayList cjArrayList = new CJArrayList(); + cjArrayList.add("first"); + cjArrayList.add("second"); + cjArrayList.add("third"); + + for (int i = 0; i < cjArrayList.size(); i++) { + System.out.println("data ="+cjArrayList.get(i)); + } + } + + @Test + public void testCJLinkList(){ + CJLinkList cjLinkList = new CJLinkList<>(); + + cjLinkList.add("111"); + cjLinkList.add("222"); + cjLinkList.add("3333"); + cjLinkList.add("444"); + +// cjLinkList.remove("3333"); + + for(int i=0;i - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/codeview/build.gradle b/codeview/build.gradle index 83b1356..174e16e 100755 --- a/codeview/build.gradle +++ b/codeview/build.gradle @@ -2,12 +2,11 @@ apply plugin: 'com.android.library' apply plugin: 'com.jfrog.bintray' apply plugin: 'com.github.dcendents.android-maven' android { - compileSdkVersion 23 - buildToolsVersion '26.0.2' + compileSdkVersion 28 defaultConfig { minSdkVersion 14 - targetSdkVersion 23 + targetSdkVersion 28 versionCode 1 versionName "1.0" } diff --git a/codeview/codeview.iml b/codeview/codeview.iml deleted file mode 100755 index 09fc9b4..0000000 --- a/codeview/codeview.iml +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index aac7c9b..0000000 --- a/gradle.properties +++ /dev/null @@ -1,17 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2fc5b57..24d508b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jan 08 16:52:25 CST 2018 +#Tue May 07 16:12:39 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/local.properties b/local.properties deleted file mode 100644 index 0890af2..0000000 --- a/local.properties +++ /dev/null @@ -1,10 +0,0 @@ -## This file is automatically generated by Android Studio. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file should *NOT* be checked into Version Control Systems, -# as it contains information specific to your local configuration. -# -# Location of the SDK. This is only used by Gradle. -# For customization when using a Version Control System, please read the -# header note. -sdk.dir=/Users/qiyue/Library/Android/sdk \ No newline at end of file diff --git a/push.sh b/push.sh deleted file mode 100755 index fce4789..0000000 --- a/push.sh +++ /dev/null @@ -1,6 +0,0 @@ -git add . -git commit -m "添加新算法" -git push -UCodeUStory -QIYUE18240332388 - diff --git a/sources/activity_onnewIntent.md b/sources/activity_onnewIntent.md new file mode 100644 index 0000000..7f46e64 --- /dev/null +++ b/sources/activity_onnewIntent.md @@ -0,0 +1,11 @@ +### Activity的onNewIntent()方法何时会被调用? + + + +前提:ActivityA已经启动过,处于当前应用的Activity堆栈中; + +当ActivityA的LaunchMode为SingleTop时,如果ActivityA在栈顶,且现在要再启动ActivityA,这时会调用onNewIntent()方法 + +当ActivityA的LaunchMode为SingleInstance,SingleTask时,如果已经ActivityA已经在堆栈中,那么此时会调用onNewIntent()方法 + +当ActivityA的LaunchMode为Standard时,由于每次启动ActivityA都是启动新的实例,和原来启动的没关系,所以不会调用原来ActivityA的onNewIntent方法 \ No newline at end of file diff --git a/sources/adsl.md b/sources/adsl.md new file mode 100644 index 0000000..3ef0a1a --- /dev/null +++ b/sources/adsl.md @@ -0,0 +1,10 @@ +#### Android Design Support Library 是Google在2015年的IO大会上,带来的全新适应Material Design设计规范的支持库。 + + +* 在这个支持库中,给我们提供了更加规范的MD设计风格控件。重要的是,Android Design Support Library中,支持所有的Android 2.1以上版本系统。在这个支持库中,主要包含下面几大控件: +Snackbar,FloatingActionButton,TextInputLayout,TabLayout,AppBarLayout,CollapsingToolbarLayout,NavigationView,CoordinatorLayout* + +- 在使用Android Design Support Library之前,我们只需要在AS中添加引用即可: + + + compile 'com.android.support:design:23.3.0' \ No newline at end of file diff --git a/sources/androidinterview/constraintLayout.md b/sources/androidinterview/constraintLayout.md new file mode 100644 index 0000000..d5991fd --- /dev/null +++ b/sources/androidinterview/constraintLayout.md @@ -0,0 +1,50 @@ +### 听说constraintLayout用起来很难 + +我们的性能比较结果表明:ConstraintLayout 在测量/布局阶段的性能比 RelativeLayout大约高 40%: + + 对齐属性 就是哪一条边和哪一条边对齐 + + + layout_constraintLeft_toLeftOf + layout_constraintLeft_toRightOf + layout_constraintRight_toLeftOf + layout_constraintRight_toRightOf + layout_constraintTop_toTopOf + layout_constraintTop_toBottomOf + layout_constraintBottom_toTopOf + layout_constraintBottom_toBottomOf + layout_constraintBaseline_toBaselineOf + layout_constraintStart_toEndOf + layout_constraintStart_toStartOf + layout_constraintEnd_toStartOf + layout_constraintEnd_toEndOf + + +1. 实现一个控件水平居中或垂直居中怎么弄? + + - RelayoutLayout + - android:layout_centerInParent="true" + - android:layout_centerVertical="true" + - android:layout_centerHorizontal="true" + + - ConstraintLayout + + - 居中 + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + + - 仅水平居中 + + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + + - 仅水垂直中 + + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + + + \ No newline at end of file diff --git a/sources/androidinterview/viewcount.md b/sources/androidinterview/viewcount.md new file mode 100644 index 0000000..42ed3e2 --- /dev/null +++ b/sources/androidinterview/viewcount.md @@ -0,0 +1,44 @@ +### 统计一个ViewGroup中包含的子View的个数(递归和非递归实现) + + + + //递归写法 + fun calculateViewCount(view:View):Int{ + if (!(view is ViewGroup)){ + return 0 + } + //统计子View数量 + var count = view.childCount + + for (i in 0 until view.childCount){ + count += calculateViewCount(view.getChildAt(i)) + } + + return count + } + + + //非递归写法 + fun calculateViewCount2(view: View): Int { + if (!(view is ViewGroup)) { + return 0 + } + + var count = 0 + val linkList = LinkedList() + + linkList.add(view) + while (!linkList.isEmpty()) { + val currentView = linkList.removeFirst() + + if (currentView is ViewGroup){ + count += currentView.childCount + for (i in 0 until currentView.childCount){ + linkList.add(currentView.getChildAt(i)) + } + } + + } + + return count + } \ No newline at end of file diff --git a/sources/androidinterview/viewdeep.md b/sources/androidinterview/viewdeep.md new file mode 100644 index 0000000..3be48ed --- /dev/null +++ b/sources/androidinterview/viewdeep.md @@ -0,0 +1,25 @@ +### 计算View的深度,主要考擦树的遍历,以及广度优先搜索 + + + fun maxDeep(view: View): Int { + //当前的view已经是最底层view了,不能往下累加层数了,返回0,代表view下面只有0层了 + if (!(view is ViewGroup)) { + return 0 + } + val vp = view as ViewGroup + //虽然是viewgroup,但是如果并没有任何子view,那么也已经是最底层view了,不能往下累加层数了,返回0,代表view下面只有0层了 + if (vp.childCount == 0) { + return 0 + } + //用来记录最大层数 + var max = 0 + //广度遍历view + //由于vp拥有子view,所以下面还有一层,因为可以+1,来叠加一层,然后再递归几岁算它的子view的层数 + for (i in 0 until vp.childCount) { + val deep = maxDeep(vp.getChildAt(i)) + 1 + if (deep > max) { + max = deep + } + } + return max + } \ No newline at end of file diff --git a/sources/androidinterview/viewgroupfindview.md b/sources/androidinterview/viewgroupfindview.md new file mode 100644 index 0000000..a98eae7 --- /dev/null +++ b/sources/androidinterview/viewgroupfindview.md @@ -0,0 +1,40 @@ +### //返回一个在vg下面的一个View,id为方法的第二个参数 + + + public static View find(ViewGroup vg, int id){ + + + } + + 可以使用的方法有: + + View -> getId() 返回一个int 的 id + ViewGroup -> getChildCount() 返回一个int的孩子数量 + ViewGroup -> getChildAt(int index) 返回一个孩子,返回值为View。 + + 这个题目就可以说非常经典了,以往的树形结构的题目,我们都是做一个二叉树的处理,除了左就是右,但是这里我们每个ViewGroup都可能有多个孩子,每个孩子既可能是ViewGroup,也可能只是View(ViewGroup是View的子类) + + + //返回一个在vg下面的一个View,id为方法的第二个参数 + public static View find(ViewGroup vg, int id){ + if(vg == null) return null; + int size = vg.getChildCount(); + //循环遍历所有孩子 + for(int i = 0 ; i< size ;i++){ + View v = vg.getChildAt(i); + //如果当前孩子的id相同,那么返回 + if(v.getId == id) return v; + //如果当前孩子id不同,但是是一个ViewGroup,那么我们递归往下找 + if(v instance of ViewGroup){ + //递归 + View temp = find((ViewGroup)v,id); + //如果找到了,就返回temp,如果没有找到,继续当前的for循环 + if(temp != null){ + return temp; + } + } + } + //到最后还没用找到,代表该ViewGroup vg 并不包含一个有该id的孩子,返回空 + return null; + } + diff --git a/sources/androidipc.md b/sources/androidipc.md new file mode 100644 index 0000000..ae6a1c2 --- /dev/null +++ b/sources/androidipc.md @@ -0,0 +1,19 @@ +### Android 进程通信方式 + + +一、使用 Intent (startActivity startService sendBroadcast) +二、使用文件共享 (SharePreference) +三、使用 Messenger +四、使用 AIDL +五、使用 ContentProvider +六、使用 Socket + + + +Messenger + +1. Messenger本质也是AIDL,只是进行了封装,开发的时候不用再写.aidl文件。 +结合我自身的使用,因为不用去写.aidl文件,相比起来,Messenger使用起来十分简单。但前面也说了,Messenger本质上也是AIDL,故在底层进程间通信这一块,两者的效率应该是一样的。 + +2. 在service端,Messenger处理client端的请求是单线程的,而AIDL是多线程的。 +使用AIDL的时候,service端每收到一个client端的请求时,就会启动一个线程(非主线程)去执行相应的操作。而Messenger,service收到的请求是放在Handler的MessageQueue里面,Handler大家都用过,它需要绑定一个Thread,然后不断poll message执行相关操作,这个过程是同步执行的。 diff --git a/sources/androidopensources/rxjavasource.md b/sources/androidopensources/rxjavasource.md new file mode 100644 index 0000000..0f3ff9f --- /dev/null +++ b/sources/androidopensources/rxjavasource.md @@ -0,0 +1,969 @@ +### RxJava 源码分析 + + +#### Rxjava中的设计模式 + +1. 适配器模式 + + 将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。 + + + + 例子 Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter emitter) throws Exception { + //源数据的分法逻辑 + emitter.onNext("Android"); + emitter.onNext("ios"); + emitter.onNext("Other"); + emitter.onComplete(); + } + }) + .map(new Function() { + @Override + public String apply(String s) { + return s+s; + } + }) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + // 提供取消钩子 + } + + @Override + public void onNext(String s) { + Log.d(TAG, "onNext: "+s); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + + + + Observable 实现了 ObservableSource接口 + + Observable.create 方法 我们期望返回一个 Observable接口对象(因为我们想链式调用),但输入的却是一个实现 ObservableOnSubscribe 接口的对象 + + 我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。 + + 所以通过ObservableCreate进行包装适配,先让ObservableCreate继承Observable,再关联ObservableOnSubscribe,然后返回ObservableCreate, + + 当观察者调用subscribe 方法 会调用 subscribeActual,同时绑定观察者和被观察者,并执行ObservableOnSubscribe重写subscribe方法 + + source(ObservableEmitter).subscribe(parent),这里就是调我们create传入的接口 + + + 1. public final void subscribe(Observer observer) { + 2. subscribeActual + 3. protected void subscribeActual(Observer observer) { + CreateEmitter parent = new CreateEmitter(observer); + observer.onSubscribe(parent); + + try { + //source就是ObservableOnSubscribe + source.subscribe(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + 4. 观察者,调用OnSubscrible实际上就是想获得观察者代理,这样就能通过代理做取消操作 + 5. 调用数据源的subscribe + 6. 数据源通过观察者适配器传递给观察者 + + 总结:数据源(是一个接口的实现,用来发射数据,以及怎么发射,单发射的实际是由别人决定的,这里就相当于实现了一个onsubscribe方法直接) + 观察者:想要获取数据源的数据,但是却不能直接去获取,需要一个中间人可能是像CreateEmitter这种发射器;而为什么不直接产生订阅关系, + 因为我们需要加一些取消的逻辑,这样可以减少用户的编写逻辑 + + //ObservableOnSubscribe 作为上层使用 + public interface ObservableOnSubscribe { + void subscribe(@NonNull ObservableEmitter e) throws Exception; + } + + // ObservableOnSubscribe作为入参 + public static Observable create(ObservableOnSubscribe source) { + ObjectHelper.requireNonNull(source, "source is null"); + return RxJavaPlugins.onAssembly(new ObservableCreate(source)); + } + + //ObservableCreate源码 作为适配器 + public final class ObservableCreate extends Observable { + final ObservableOnSubscribe source; + + public ObservableCreate(ObservableOnSubscribe source) { + this.source = source; + } + + @Override + protected void subscribeActual(Observer observer) { + CreateEmitter parent = new CreateEmitter(observer); + observer.onSubscribe(parent); + + try { + //产生订阅后开始调用源数据的处理 + source.subscribe(parent); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + parent.onError(ex); + } + } + + + + public final void subscribe(Observer observer) { + ObjectHelper.requireNonNull(observer, "observer is null"); + try { + observer = RxJavaPlugins.onSubscribe(this, observer); + + ObjectHelper.requireNonNull(observer, "Plugin returned null Observer"); + + subscribeActual(observer); + } catch (NullPointerException e) { // NOPMD + throw e; + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + // can't call onError because no way to know if a Disposable has been set or not + // can't call onSubscribe because the call might have set a Subscription already + RxJavaPlugins.onError(e); + + NullPointerException npe = new NullPointerException("Actually not, but can't throw other exceptions due to RS"); + npe.initCause(e); + throw npe; + } + } + + +2. 同时我们适配方法调用,重写subscribeActual方法,当调用Obserable.subscribe时,再传递给subscribeActual,将Observer 传递ObservableOnSubscribe + + 但ObservableOnSubscribe 的OnSubscribe 需要ObserverEmitter(因为我们要,使用onNext,OnError,OnComplete方法,去传递数据,并且我们要在每一种操作判断 + dispose状态),所以使用CreateEmitter完成适配,所以这里也是适配器模式 + + ** 有人会疑惑这里是不是代理模式,CreateEmitter是不是代理? + + 答案No,首先如果CreateEmitter是代理,那么CreateEmitter和Observer应该有实现相同的接口 + + ** 代理提供的接口和原本的要实现统一接口,代理模式的作用是不把实现直接暴露给client,而是通过代理这个层,代理能够做一些处理,判断。** + + ** 适配器模式体现的是适配,比如Client 需要A类提供的行为,此时我们有B类提供了一些方法可以实现,但是方法名字不一样,需要改造一下变成A, + 此时创建一个类C,实现A接口,并注入B类,这样相当于C就通过协调用B的方法,来补充到A接口方法中(所以这里C属于适配器,来协调Client 和 B) + 对适配器模式的功能很好理解,就是把一个类的接口变换成客户端所能接受的另一种接口 + + + + + //Emitter + public interface Emitter { + + void onNext(@NonNull T value); + + void onError(@NonNull Throwable error); + + void onComplete(); + } + + //ObservableEmitter + public interface ObservableEmitter extends Emitter { + + + //CreateEmitter + static final class CreateEmitter extends AtomicReference + implements ObservableEmitter, Disposable { + + + private static final long serialVersionUID = -3434801548987643227L; + + final Observer observer; + + CreateEmitter(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T t) { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + if (!isDisposed()) { + observer.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (!isDisposed()) { + try { + observer.onError(t); + } finally { + dispose(); + } + return true; + } + return false; + } + + @Override + public void onComplete() { + if (!isDisposed()) { + try { + observer.onComplete(); + } finally { + dispose(); + } + } + } + + @Override + public void setDisposable(Disposable d) { + DisposableHelper.set(this, d); + } + + @Override + public void setCancellable(Cancellable c) { + setDisposable(new CancellableDisposable(c)); + } + + @Override + public ObservableEmitter serialize() { + return new SerializedEmitter(this); + } + + @Override + public void dispose() { + DisposableHelper.dispose(this); + } + + @Override + public boolean isDisposed() { + return DisposableHelper.isDisposed(get()); + } + } + + AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。 + ------ + + //线程安全的改变引用 + public static boolean dispose(AtomicReference field) { + Disposable current = field.get(); + Disposable d = DISPOSED; + if (current != d) { + // 设置新的值,返回旧值 + current = field.getAndSet(d); + if (current != d) { + // 如果旧值和新值不一样,旧值就dispose + if (current != null) { + current.dispose(); + } + return true; + } + } + return false; + } + + +3. 观察者模式 + + + + 经常提到观察者与被观察者,这不就是JAVA的观察者模式的运用么?是的,但是跟传统意义的上观察者模式还不太一样,所以Rxjava实际上是一种扩展的观察者模式,所以有必要对这个扩展的观察者模式做进一步的了解。 + + + 这看起来很像设计模式的观察者模式,但是有个重要的区别之一在于在没有Subscriber之前,Observable不会产生事件。 + + 对于普通的观察者模式这里不多说,简单概念它就是:观察者(Observer)需要在被观察者(Observable)变化的一顺间做出反应。而两者通过注册(Register)或者订阅(Subscrible)的方式进行绑定 + + + Observer与Observable是通过subscrible()来达成订阅关系。 + Rxjava中的事件回调有三种:onNext()、onCompleted()、onError()。 + 如果一个Observable没有任何的Observer,那么这个Observable是不会发出任何事件的。 + +4. 装饰器模式 + + + // 表示包括T在内的任何T的父类,包括R在内任何子类 + public final Observable map(Function mapper) { + ObjectHelper.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableMap(this, mapper)); + } + + // map 后会重新返回一个Observable 这个观察的数据是新的map装换后的数据R + + public final class ObservableMap extends AbstractObservableWithUpstream { + final Function function; + + public ObservableMap(ObservableSource source, Function function) { + super(source); + this.function = function; + } + + @Override + public void subscribeActual(Observer t) { + source.subscribe(new MapObserver(t, function)); + } + + + static final class MapObserver extends BasicFuseableObserver { + final Function mapper; + + MapObserver(Observer actual, Function mapper) { + super(actual); + this.mapper = mapper; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + actual.onNext(null); + return; + } + + U v; + + try { + v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value."); + } catch (Throwable ex) { + fail(ex); + return; + } + actual.onNext(v); + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Nullable + @Override + public U poll() throws Exception { + T t = qs.poll(); + return t != null ? ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value.") : null; + } + } + } + + + +#### 自定义产生数据源可以使用Observable.create,RxJava内部提供了一些默认的方法 + +1. Observable.fromArray + + + public static Observable fromArray(T... items) { + ObjectHelper.requireNonNull(items, "items is null"); + if (items.length == 0) { + return empty(); + } else + if (items.length == 1) { + return just(items[0]); + } + return RxJavaPlugins.onAssembly(new ObservableFromArray(items)); + } + + ObservableFromArray + + public final class ObservableFromArray extends Observable { + final T[] array; + public ObservableFromArray(T[] array) { + this.array = array; + } + @Override + public void subscribeActual(Observer s) { + FromArrayDisposable d = new FromArrayDisposable(s, array); + + s.onSubscribe(d); + + if (d.fusionMode) { + return; + } + + d.run(); + } + + static final class FromArrayDisposable extends BasicQueueDisposable { + + final Observer actual; + + final T[] array; + + int index; + + boolean fusionMode; + + volatile boolean disposed; + + FromArrayDisposable(Observer actual, T[] array) { + this.actual = actual; + this.array = array; + } + / + . + . + . + 省略部分代码 + . + / + + void run() { + T[] a = array; + int n = a.length; + + for (int i = 0; i < n && !isDisposed(); i++) { + T value = a[i]; + if (value == null) { + actual.onError(new NullPointerException("The " + i + "th element is null")); + return; + } + actual.onNext(value); + } + if (!isDisposed()) { + actual.onComplete(); + } + } + } + } + + + // 核心还是通过 ObservableFromArray 实现一个适配器,将数组数据包装起来, + // 调用subscribe 会调用subscribeActual ,先将传递的Observer进行包装适配,然后订阅建立关联,同时调用FromArrayDisposable 的run方法 + + // 还是两个适配器,一个是ObservableFromArray适配传递过来数据,一个是FromArrayDisposable适配接收的Observer,同时封装分发逻辑对应run实现 + + + +- doOnNext doOnError 是增强实现,在onNext前面执行 ;通常做一些输出,打印日志,数据存储,备份 + + + doOnNext + + private Observable doOnEach(Consumer onNext, Consumer onError, Action onComplete, Action onAfterTerminate) { + ObjectHelper.requireNonNull(onNext, "onNext is null"); + ObjectHelper.requireNonNull(onError, "onError is null"); + ObjectHelper.requireNonNull(onComplete, "onComplete is null"); + ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); + return RxJavaPlugins.onAssembly(new ObservableDoOnEach(this, onNext, onError, onComplete, onAfterTerminate)); + } + + public final class ObservableDoOnEach extends AbstractObservableWithUpstream { + + source.subscribe(new DoOnEachObserver(t, onNext, onError, onComplete, onAfterTerminate)) + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + //这里相当于AOP思想,起到一个拦截,增强扩展功能,执行时机就是在本次Observable的onNext方法后执行 + onNext.accept(t); + } catch (Throwable e) { + Exceptions.throwIfFatal(e); + s.dispose(); + onError(e); + return; + } + + actual.onNext(t); + } + + +### RxJava 设计理念(套娃的设计和组装) + + 操作组合设计是从上到下的,即所谓的套娃制作设计,从里到外(这种装饰者模式一层一层就是套娃的设计思想) + + 这种设计把第一个Observable想象成第一个最小的套娃,把第二个Observable比第一个大一点套娃,第三个Observable比第二个还要大一点,这就是套娃的设计思想 + 想象每个套娃都那么几个洞;每层组装都会顺着洞放下一个钩子滑轮(钩子在下,绕滑轮一圈)一层勾着一层,当第一个层被第二层嵌套是,就将准备好的东西放到勾着上,所以每层之间就有了钩子滑轮着作为桥梁; + + 当最外层产生订阅的时候我们相当,上面的人通知下一层,下一次同样通知下一次,最终底层接到通知,的钩子被拉动,东西顺着钩子到达上面一层,然后一层一层往上, + + 钩子相当于Observer, 通知者相当于 subscribe 调用Observer onNext, (subscribe相当于另一个绳子东西,可以通知下一层) + + 通过另一个根绳子subscribe通知下一次,直到底层的Observable,接到命令后,调用Observer OnNext,相当于拉动滑轮一层往上一层传递 + + //每一层调用这样的方法将Observer传递给subscribe方法,再传递给subscribeActual,再调用下一层的 source.subscribe(parent),同时parent,相当于有封装了一层 + source.subscribe(parent); + + +### 订阅后产生的执行,还是很复杂的一件事,我们继续探讨 + + 先看一个简单的例子:模拟延迟5秒发送一个"HelloWorold",在5秒内点击按钮取消订阅 + + fun testSimple() { + val observable = Observable.create { + Log.i("rxjava", "oncreate") + Handler().postDelayed({ + it.onNext("HelloWorld") + }, 5000) + + } + var disposeObserver = observable.subscribeWith(DisposableObserverImpl()) + btn_cancel.setOnClickListener { + disposeObserver.dispose() + } + } + + public static boolean dispose(AtomicReference field) { + Disposable current = field.get(); + Disposable d = DISPOSED; + if (current != d) { + current = field.getAndSet(d); + if (current != d) { + if (current != null) { + current.dispose(); + } + return true; + } + } + return false; + } + + + - 执行流程 + 1. subscribeWith + 2. ObservableCreate.subscribe --> subscribeActual -->创建一个Disposable(CreateEmitter) -->observer.onSubscribe(CreateEmitter); + 3. DisposableObserver onSubscribe 收到ObservableCreate传递过来的Disposable,更新自己的value(因为自己本身也是一个Disposable) + 4. ObservableCreate.source.subscribe(CreateEmitter);产生实际订阅,开始按照要就发射数据,这里做了5秒延迟 + 5. 此时延迟时间内调用 DisposableObserver.dispose() + 6. DisposableHelper.dispose(s); 这个s 就是DisposableObserver内部的AtomicReference s = new AtomicReference(); + 7. 提醒一句,之前这个s 通过onSubscribe已经被赋值成CreateEmitter,因为CreateEmitter本身也实现了Disposable + 8. Disposable current = field.get(); 此时current 值 CreateEmitter + 9. current!=DISPOSED 开始更新 field.getAndSet(d) 更新成功返回旧值 + 10. 判断一下旧值不等于null,而是CreateEmitter,所以继续调用CreateEmitter.dispose() + 11. CreateEmitter.dispose() --> DisposableHelper.dispose(this) + 12. field.get() 当前的的value默认是null的,然后阐释更新成DISPOSED + 13. CreateEmitter在OnNext过来的时候就会验证这个值,已经DISPOSED就不再传递 + + 所以多层嵌套也是如此,通过每个外层套娃Disposable 的 AtomicReference储存下一层套娃的Disposable; + 当外层出发dispose就一层一层的更新AtomicReference为DESTORY,并且返回下一层的Disposable,如果不为空就证明没有到达最底层, + 所以继续调用disposable,直到返回的数据为null为止,从而控制了整个流程的取消订阅 + + //核心方法 subscribe 用来向底层套娃通知,通知到最后底层时,底层套娃通过 OnNext 等方法开始通知上一层观察者,上一层再通过OnNext通知上一层 + //onSubscribe 在通知底层过程中,每一层告知上一层一个Dispose对象 + + - 套娃的设计其实就是装饰者模式,Observable一层嵌套一层,同时在订阅观察者也会被一层一层嵌套发送到底部 + + +- 简单回顾一下前面的源码间设计 + + - Observable.create - ObservableOnSubscribe接口 -> ObservableCreate适配器(继承Observable) -> 外部调用subscribe ->真实调用subscribeActual + ->CreateEmitter(适配) -> Observer + + - Observable.fromArray -> ObservableFromArray适配器(继承Observable) -> 外部调用subscribe ->真实调用subscribeActual + ->FromArrayDisposable() -> Observer + + - Observable.map -> ObservableMap适配器(继承Observable)-> 外部调用subscribe ->真实调用subscribeActual ->MapObserver + + - Observable.filter ->ObservableFilter -> 外部调用subscribe ->真实调用subscribeActual ->FilterObserver + + - Observable 的所有操作方法都是通过包装自己来实现的,所以设计就像是套娃的设计,当我们执行的时候通过subscrible + + - 看一个例子 + + + val observable = Observable.create { + Log.i("rxjava", "oncreate") + it.onNext("HelloWorld") + }.doOnNext { + + }.filter { it == "HelloWorld" }.map { "AAA$it" } + + // 产生订阅后执行逻辑比较复杂, + + 从最外层调用subscrible开始 会调用 Observer开始调用 + ObservableObserveOn.subscribe(DisposableObserver) + + ObservableObserveOn.subscribeActual source.subscribe() + + ObservableSubscribeOn.subscribeActual s.onSubscribe(SubscribeOnObserver); + + ObservableObserveOn.ObserveOnObserver.onSubscribe() actual.onSubscribe(ObserveOnObserver) + + DisposableObserver.onSubscribe AtomicReference s 得到 Disposable(ObserveOnObserver) + + //开始提交任务,此时的任务就可能是异步的了,之前都是同步任务 + ObservableSubscribeOn -> 在任务中执行 source.subscribe(parent); + + ObservableCreate.subscribeActual observer.onSubscribe(CreateEmitter); + + SubscribeOnObserver.onSubscribe + +- Observable.cache()方法,当多个观察者订阅后,被订阅的观察者只会执行一次,然后缓存到内存中 + + cache() -> ObservableCache.from(this)-> from(source, 16) ->CacheState(source, capacityHint) ->ObservableCache -> + + CacheState(CacheState extends LinkedArrayList implements Observer) + + 这里这么实现,比平时创建多了一层,Observer,当订阅者产生订阅的时候,调用subscrible时,会想通过CacheState这个Observer做一层代理, + + 其中 CacheState 中 有一个集合 final AtomicReference[]> observers; 在addChild添加进去;这里 + ReplayDisposable装饰了cacheState,和最终的Observer, 当调用replay 时,从state中取到元素o,然后通过NotificationLite.accept(o, child) + 回调onNext等方法 + + 每一个观察者订阅时,都会先包装成ReplayDisposable ,并且addChild保存到CacheState中,接着调用ReplayDisposable的replay处理数据回调 + + + + + - 这里会有个问题,ObservableCache里的subscribeActual方法为什么不是用的CreateEmitter实现的,而是通过state.connect , + 这里this 其实就是一个Observer; 因为我们ObservableCache是通过包装扩展来的,所以前一个Observable, + 产生订阅的地方一定是需要一个Observer,而不是需要一个Emitter(如果是原始对象,那是Emitter,但现在不是),但这里我们同样需要一个实现Disposable + 接口的一个对象,这样可以做取消操作。 + + + + - Code + + public void connect() { + source.subscribe(this); + isConnected = true; + } + + @Override + protected void subscribeActual(Observer t) { + // we can connect first because we replay everything anyway + ReplayDisposable rp = new ReplayDisposable(t, state); + t.onSubscribe(rp); + + state.addChild(rp); + + // we ensure a single connection here to save an instance field of AtomicBoolean in state. + if (!once.get() && once.compareAndSet(false, true)) { + state.connect(); + } + + rp.replay(); + } + + + +- Observer 设计详解 + + + public interface Observer { + //产生订阅后回传一个Disposable + void onSubscribe(@NonNull Disposable d); + + void onNext(@NonNull T t); + + void onComplete(); + + } + + + +- Disposable 设计详解 + + - 目的: + + + + 主要功能是管理一个状态值。下面再来回顾一下Observable的作用,即其发布数据并由Observer消费。作为消费者, + 我很可能不需要每次都对接收的数据进行处理,也就是消费者应该有一个可以放弃操作资源的可选项, + 而我们在Observer接口定义中并没有找到相关的方法。在这里可以拓展一个设计理念:一切为了解耦。我们在做表设计的时候,尽量保证表符合三范式, + 以达到表的单一性,用生活中的话说就是请专注于自己的领域。 + + + + + // 一次性的 用完即可丢弃的,每个Observer适配器都会实现一个Disposable + public interface Disposable { + //处理,用来解除订阅,防止内存泄露 + void dispose(); + //判断是否已经被处理了 + boolean isDisposed(); + } + + + // 上面讲到的 CreateEmitter 在调用真实Observer 前都做了isDispose判断 + + static final class CreateEmitter extends AtomicReference + implements ObservableEmitter, Disposable { + + +- 线程切换 + + +- ConnectableObservable + + + +- Subject模式 + + Subject实际上还是Observable,只不过它继承了Observer接口,可以通过onNext、onComplete、onError方法发射和终止发射数据。 + + + 1. PublishSubject 这个是主动的推送的 ,之前作为被观察者只能在订阅的时候出发,现在自己本身实现了Observer,所以可以主动去发送数据给订阅者 + + PublishSubject 是最直接的一个 Subject。当一个数据发射到 PublishSubject 中时,PublishSubject 将立刻把这个数据发射到订阅到该 subject 上的所有 subscriber 中。 + + 同时:PublishSubject特性,先发射的数据,后来的观者者是不会收到任何数据的 + + + public final class PublishSubject extends Subject { + + PublishSubject() { + //内部保存一个观察者列表 + subscribers = new AtomicReference[]>(EMPTY); + } + + 当有新的订阅者订阅的时候 + @Override + public void subscribeActual(Observer t) { + PublishDisposable ps = new PublishDisposable(t, this); + t.onSubscribe(ps); + //保存订阅者到列表中 + if (add(ps)) { + // if cancellation happened while a successful add, the remove() didn't work + // so we need to do it again + //添加成功后,因为添加过程也是一个自旋的过程,在这个过程中ps可能会被更改,被取消了就会移除 + if (ps.isDisposed()) { + remove(ps); + } + } else { + Throwable ex = error; + if (ex != null) { + t.onError(ex); + } else { + t.onComplete(); + } + } + } + + void remove(PublishDisposable ps) { + for (;;) { + PublishDisposable[] a = subscribers.get(); + if (a == TERMINATED || a == EMPTY) { + return; + } + + int n = a.length; + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == ps) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + PublishDisposable[] b; + + if (n == 1) { + b = EMPTY; + } else { + b = new PublishDisposable[n - 1]; + //a旧数组,b新数组 + //src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,destPos表示目标数组放入的起始位置, + //length表示要复制的长度。 + //当前j表示找到了需要移除的位置 + System.arraycopy(a, 0, b, 0, j); + //丢弃j位置袁术,所以从j+1开始复制剩余元素 + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + //原子化操作,判断这个操作的集合还是不是之前的集合,这里比较的是地址,证明没人操作过, + //如果多线程,有人操作过就会返回false,就会重新循环获取被改动的集合,继续操作 + if (subscribers.compareAndSet(a, b)) { + return; + } + } + } + + + boolean add(PublishDisposable ps) { + for (;;) { + PublishDisposable[] a = subscribers.get(); + if (a == TERMINATED) { + return false; + } + + int n = a.length; + @SuppressWarnings("unchecked") + PublishDisposable[] b = new PublishDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = ps; + //同样判断当前集合有没有更改过,如果没有就赋值,有就修改 + if (subscribers.compareAndSet(a, b)) { + return true; + } + } + } + + + 总结:PublishSubject很好的使用了CAS自旋来保证线程安全,因为数组不会很大所以使用arraycopy这种内存抖动也不会很大,实现原理类似CopyonWriteArray + + 2. ReplaySubject 可以缓存所有发射给他的数据。当一个新的订阅者订阅的时候,缓存的所有数据都会发射给这个订阅者。 由于使用了缓存,所以每个订阅者都会收到所以的数据: + + + public static ReplaySubject create() { + return new ReplaySubject(new UnboundedReplayBuffer(16)); + } + + @Override + protected void subscribeActual(Observer observer) { + ReplayDisposable rs = new ReplayDisposable(observer, this); + observer.onSubscribe(rs); + + if (!rs.cancelled) { + //存储观察者 + if (add(rs)) { + if (rs.cancelled) { + remove(rs); + return; + } + } + //开始将存储数据发送 + buffer.replay(rs); + } + } + + + @Override + public void onNext(T t) { + if (t == null) { + onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); + return; + } + if (done) { + return; + } + + ReplayBuffer b = buffer; + //存储发送数据 + b.add(t); + + for (ReplayDisposable rs : observers.get()) { + //发送 + b.replay(rs); + } + } + //其中每次订阅和发送数据,都会把之前的发给观察者,观察者自身有个index,默认是0,存储的是数据的位置,如果已经收到过的数据index也跟着1+, + public void replay(ReplayDisposable rs) { + // 多个线程进入这个值让后续的线程等待 + if (rs.getAndIncrement() != 0) { + return; + } + + int missed = 1; + final List b = buffer; + final Observer a = rs.actual; + + Integer indexObject = (Integer)rs.index; + int index; + if (indexObject != null) { + index = indexObject; + } else { + index = 0; + rs.index = 0; + } + + for (;;) { + + if (rs.cancelled) { + rs.index = null; + return; + } + + int s = size; + + while (s != index) { + + if (rs.cancelled) { + rs.index = null; + return; + } + + Object o = b.get(index); + + if (done) { + if (index + 1 == s) { + s = size; + if (index + 1 == s) { + if (NotificationLite.isComplete(o)) { + a.onComplete(); + } else { + a.onError(NotificationLite.getError(o)); + } + rs.index = null; + rs.cancelled = true; + return; + } + } + } + + a.onNext((T)o); + index++; + } + + if (index != size) { + continue; + } + + rs.index = index; + //如果还有新的线程没有执行,继续执行 + missed = rs.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + 总结:非常巧妙的使用CAS实现了线程安全的操作 + + 3. BehaviorSubject 只保留最后一个值。 等同于限制 ReplaySubject 的个数为 1 的情况。在创建的时候可以指定一个初始值,这样可以确保党订阅者订阅的时候可以立刻收到一个值 + + 4. AsyncSubject + AsyncSubject 也缓存最后一个数据。区别是 AsyncSubject 只有当数据发送完成时(onCompleted 调用的时候)才发射这个缓存的最后一个数据。可以使用 AsyncSubject 发射一个数据并立刻结束。 + + AsyncSubject s = AsyncSubject.create(); + s.subscribe(v -> System.out.println(v)); + s.onNext(0); + s.onCompleted(); + s.onNext(1); + s.onNext(2); + + + 结果: + + 0 +#### 异步任务切换 + + subscribeOn 将subscribe方法切换到指定线程 + observeOn 将OnNext调用的数据切换线程,也就是观察者回调方法 + + +#### compose + + //自定义transform ,封装公共的操作变换 + //.compose(customTransformer()) + fun customTransformer(): ObservableTransformer { + return ObservableTransformer { upstream -> + upstream.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + } + } \ No newline at end of file diff --git a/sources/app_start_step.md b/sources/app_start_step.md new file mode 100644 index 0000000..805628c --- /dev/null +++ b/sources/app_start_step.md @@ -0,0 +1,27 @@ + +APP启动流程 + + +#### 整个应用程序的启动过程要执行很多步骤,但是整体来看,主要分为以下五个阶段: + + + 一. Step1 - Step 11: + Launcher通过Binder进程间通信机制通知ActivityManagerService, + 它要启动一个Activity; + + 二. Step 12 - Step 16: + ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态; + + 三. Step 17 - Step 24: + Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态, + 于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例, + 即将要启动的Activity就是在这个ActivityThread实例中运行; + + 四. Step 25 - Step 27: + ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService, + 以便以后ActivityManagerService能够通过这个Binder对象和它进行通信; + + 五. Step 28 - Step 35: + ActivityManagerService通过Binder进程间通信机制通知ActivityThread, + 现在一切准备就绪,它可以真正执行Activity的启动操作了。 + 。 \ No newline at end of file diff --git a/sources/application.md b/sources/application.md new file mode 100644 index 0000000..8604f9f --- /dev/null +++ b/sources/application.md @@ -0,0 +1,62 @@ +#### Application 多进程问题 + +在做项目时,遇到一个大坑,就是我的APP 的Application 的onCreate方法,竟然执行了好几次,这就导致我在onCreate里面做了一些初始化的操作被重复执行了,导致奇怪的bug产生。后来冷静下来分析一下,才发现有一些第三方组件,比如百度推送之类的,它们是单独开了一个进程,那么每个进程会自己初始化自己的Application,那自然onCreate方法会多次执行。准确的说就是你的APP里有多少个进程,就会初始化多少次Application 。 + +但是有的东西就是只需要在Application 的onCreate 里只初始化一次。那怎么解决呢?看代码: + + + + + public class MyApplication extends Application { + private final static String PROCESS_NAME = "com.test"; + private static MyApplication myApplication = null; + + public static MyApplication getApplication() { + return myApplication; + } + + /** + * 判断是不是UI主进程,因为有些东西只能在UI主进程初始化 + */ + public static boolean isAppMainProcess() { + try { + int pid = android.os.Process.myPid(); + String process = getAppNameByPID(MyApplication.getApplication(), pid); + if (TextUtils.isEmpty(process)) { + return true; + } else if (PROCESS_NAME.equalsIgnoreCase(process)) { + return true; + } else { + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return true; + } + } + + /** + * 根据Pid得到进程名 + */ + public static String getAppNameByPID(Context context, int pid) { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (android.app.ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { + if (processInfo.pid == pid) { + return processInfo.processName; + } + } + return ""; + } + + @Override + public void onCreate() { + super.onCreate(); + + myApplication = this; + + if (isAppMainProcess()) { + //do something for init + //这里就只会初始化一次 + } + } + } \ No newline at end of file diff --git a/sources/application_service.md b/sources/application_service.md new file mode 100644 index 0000000..d4c12a5 --- /dev/null +++ b/sources/application_service.md @@ -0,0 +1,42 @@ +### application 开线程可以替换Service处理后台任务吗 + + +#### 可以,但是有缺点 + + +1. Application中初始化太多东西,会导致app启动速度变慢(开启一个线程没什么,开启多个就会有问题) + +2. Application生命周期过长,Application生命周期和应用的什么周期一样,如果后台任务不需要这么长的生命周期,用IntentService可以实现完成后自动关闭,自动同步,远比Application好 + +3. Application生命周期无法延续,在被强杀后,后台任务会关闭;而服务可以指定新进程进行保活。考虑音乐播放器的功能,有些音乐播放器,即使应用退出后依然可以进行前台服务的保留以便于随时恢复 + +总结 + +1.会降低性能 + +2.Application提供的生命周期自由度不足 + +3.Application中开启子线程其实是很不错的,因为Service开销太大 + +#### Application 生命周期 + +1. Application 生命周期和应用的生命周期一样,应用的声明周期是怎么样的呢?? + + 你是否还天真的以为我们在按返回键退出最后一个activity应用程序就退出了?答案是否定的 + + 当我们退出最后一个Activity时,应用程序还没有退出,通过重写Application可以看出,再次进入应用,Application 并没有重新初始化。 + +2. 如果真正退出一个应用程序? + + 在最后一个Activity的onDestory中 System.exit(0),可以正常退出应用,再次进入应用时Application的onCreate会重新执行 + +3. System.exit(0) 注意事项 + + - 1. System.exit(0) 传入非0表示正常退出 + + - 2. System.exit(0) 只能退出当前进程,如果进程中启动了一个服务,在新的进程,新的服务不会被杀死,此服务只能通过StopService停止 + + - 3. System.exit(0) 时当程序中有startService启动一个服务,服务没有开启新的进程,此时,服务会被重启,重新走onCreate等生命周期方法,并且应用进程也会被重启,Application也会重新onCreate初始化 + + + diff --git a/sources/asynctask.md b/sources/asynctask.md new file mode 100644 index 0000000..e1ffacb --- /dev/null +++ b/sources/asynctask.md @@ -0,0 +1,96 @@ +### AsyncTask 源码分析 + + +1. 面根据CPU数量创建一个 2到4的核心线程池,最大线程池时CPU*2 +1,存货30秒,队列为128 +并且默认是Serial_Excutor,而非并发 + + +线程一个一个的执行,通过来一个请求让其加入到一个队列中,然后在这个请求执行完后去执行下一个 + +这里设计很气面,同时设置一个变量判断当前是否有正在执行的 + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + // 第一次,或者前面都执行完了 + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + + + + + + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + // We want at least 2 threads and at most 4 threads in the core pool, + // preferring to have 1 less than the CPU count to avoid saturating + // the CPU with background work + private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE_SECONDS = 30; + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + public Thread newThread(Runnable r) { + return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); + } + }; + + private static final BlockingQueue sPoolWorkQueue = + new LinkedBlockingQueue(128); + + /** + * An {@link Executor} that can be used to execute tasks in parallel. + */ + public static final Executor THREAD_POOL_EXECUTOR; + + static { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, + sPoolWorkQueue, sThreadFactory); + threadPoolExecutor.allowCoreThreadTimeOut(true); + THREAD_POOL_EXECUTOR = threadPoolExecutor; + } + + + + private static class SerialExecutor implements Executor { + final ArrayDeque mTasks = new ArrayDeque(); + Runnable mActive; + + public synchronized void execute(final Runnable r) { + mTasks.offer(new Runnable() { + public void run() { + try { + r.run(); + } finally { + scheduleNext(); + } + } + }); + if (mActive == null) { + scheduleNext(); + } + } + + protected synchronized void scheduleNext() { + if ((mActive = mTasks.poll()) != null) { + THREAD_POOL_EXECUTOR.execute(mActive); + } + } + } \ No newline at end of file diff --git a/sources/automizationoperation.md b/sources/automizationoperation.md new file mode 100644 index 0000000..1ee22c9 --- /dev/null +++ b/sources/automizationoperation.md @@ -0,0 +1,56 @@ + +### 多个安全的原子化操作组合将不是一个线程安全的,很多时候你错误用到了这一点 + +1. 比如 + + + fun main() { + test() + } + + fun test() { + + var list = Vector() + for (i in 0..9999) { + list.add("string$i") + } + + Thread(Runnable { + while (true) { + if (list.size > 0) { + val content = list.get(list.size - 1) + } else { + break + } + } + }).start() + + Thread(Runnable { + while (true) { + if (list.size <= 0) { + break + } + list.removeAt(0) + try { + Thread.sleep(10) + } catch (e: InterruptedException) { + e.printStackTrace() + } + + } + }).start() + + + /** + * Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9999 + at java.util.Vector.get(Vector.java:748) + at com.wangpos.datastructure.java.thread.AutomizationoperationKt$test$1.run(automizationoperation.kt:23) + at java.lang.Thread.run(Thread.java:745) + */ + } + + + + 结果会出现线程安全问题,list.size 、 list.removeAt(0) 、list.get() 里面都有线程控,单独都可以看做安全的原子化操作, + + 但是合并一起后就必须保证合并后的是一个原子化操作,必须保证线程安全,否者就会报错,所以上面合并后的操作要加锁来保证原子化操作 \ No newline at end of file diff --git a/sources/binder.md b/sources/binder.md new file mode 100644 index 0000000..13abcf8 --- /dev/null +++ b/sources/binder.md @@ -0,0 +1,324 @@ + +1. Binder 概述 +简单介绍下什么是 Binder。Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现;OpenBinder 起初由 Be Inc. 开发,后由 Plam Inc. 接手。从字面上来解释 Binder 有胶水、粘合剂的意思,顾名思义就是粘和不同的进程,使之实现通信。对于 Binder 更全面的定义,等我们介绍完 Binder 通信原理后再做详细说明。 + +2. 为什么必须理解 Binder ? +作为 Android 工程师的你,是不是常常会有这样的疑问: + + 为什么 Activity 间传递对象需要序列化? + + Activity 的启动流程是什么样的? + + 四大组件底层的通信机制是怎样的? + + AIDL 内部的实现原理是什么? + + 插件化编程技术应该从何学起? + +这些问题的背后都与 Binder 有莫大的关系,要弄懂上面这些问题理解 Binder 通信机制是必须的。 + +我们知道 Android 应用程序是由 Activity、Service、Broadcast Receiver 和 Content Provide 四大组件中的一个或者多个组成的。 +有时这些组件运行在同一进程,有时运行在不同的进程。这些进程间的通信就依赖于 Binder IPC 机制。不仅如此,Android 系统对应用层提供的各种服务如:ActivityManagerService、PackageManagerService 等都是基于 Binder IPC 机制来实现的。Binder 机制在 Android 中的位置非常重要,毫不夸张的说理解 Binder 是迈向 Android 高级工程的第一步。 + +3. 为什么是 Binder ? +Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢? + +主要是基于性能、稳定性和安全性几方面的原因。 + +- 性能 + +首先说说性能上的优势。Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。 + + + IPC方式 数据拷贝次数 + 共享内存 0 + Binder 1 + Socket/管道/消息队列 2 + +- 稳定性 + +再说说稳定性 + + Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。 + + 共享内存虽然无需拷贝,但是控制负责,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的。 + +- 安全性 + +另一方面就是安全性。Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。 + +基于上述原因,Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求,这就是 Binder。 + +最后用一张表格来总结下 Binder 的优势: + +优势 描述 + + 性能 只需要一次数据拷贝,性能上仅次于共享内存 + 稳定性 基于 C/S 架构,职责明确、架构清晰,因此稳定性好 + 安全性 为每个 APP 分配 UID,进程的 UID 是鉴别进程身份的重要标志 + +二. Linux 下传统的进程间通信原理 +了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此,在介绍 Binder 跨进程通信原理之前,我们先聊聊 Linux 系统下传统的进程间通信是如何实现。 + +2.1 基本概念介绍 +这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍,然后逐步展开,向大家说明传统的进程间通信的原理。 + +Linux 背景知识 + +上图展示了 Liunx 中跨进程通信涉及到的一些基本概念: + +进程隔离 +进程空间划分:用户空间(User Space)/内核空间(Kernel Space) +系统调用:用户态/内核态 +进程隔离 +简单的说就是操作系统中,进程与进程间内存是不共享的。两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。 + +进程空间划分:用户空间(User Space)/内核空间(Kernel Space) +现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。 + +简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。 + +图片来自网络 + +系统调用:用户态与内核态 +虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。 + +Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。 + +当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。 + +当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。 + +系统调用主要通过如下两个函数来实现: + +copy_from_user() //将数据从用户空间拷贝到内核空间 +copy_to_user() //将数据从内核空间拷贝到用户空间 +2.2 Linux 下的传统 IPC 通信原理 +理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。 + +通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。如下图: + +传统 IPC 通信原理 + +这种传统的 IPC 通信方式有两个问题: + +性能低下,一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝; +接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。 +三. Binder 跨进程通信原理 +理解了 Linux IPC 相关概念和通信原理,接下来我们正式介绍下 Binder IPC 的原理。 + +3.1 动态内核可加载模块 && 内存映射 +正如前面所说,跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。 + +在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder 驱动(Binder Dirver)。 + +那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过两次拷贝来实现吗?显然不是,否则也不会有开篇所说的 Binder 在性能方面的优势了。 + +这就不得不通道 Linux 下的另一个概念:内存映射。 + +Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。 + +内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。 + +3.2 Binder IPC 实现原理 +Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。 + +比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。 + +而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。 + +一次完整的 Binder IPC 通信过程通常是这样: + +首先 Binder 驱动在内核空间创建一个数据接收缓存区; +接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系; +发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。 +如下图: + +Binder IPC 原理 + +四. Binder 通信模型 +介绍完 Binder IPC 的底层通信原理,接下来我们看看实现层面是如何设计的。 + +一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方必然需要借助 Binder 来实现。 + +4.1 Client/Server/ServiceManager/驱动 +前面我们介绍过,Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。 + + + +Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。 + +通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 www.google.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了 www.google.com 对应的 ip 地址 10.249.23.13,然后通过这个 ip 地址才能放到到 www.google.com 对应的服务器。 + +互联网通信模型 + +Android Binder 设计与实现一文中对 Client、Server、ServiceManager、Binder 驱动有很详细的描述,以下是部分摘录: + +Binder 驱动 +Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。 + +ServiceManager 与实名 Binder +ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。 + +细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。 + +Client 获得实名 Binder 的引用 +Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。 + +4.2 Binder 通信过程 +至此,我们大致能总结出 Binder 通信过程: + +首先,一个进程使用 BINDER_SET_CONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager; +Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。 +Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。 +我们看到整个通信过程都需要 Binder 驱动的接入。下图能更加直观的展现整个通信过程(为了进一步抽象通信过程以及呈现上的方便,下图我们忽略了 Binder 实体及其引用的概念): + +Binder 通信模型 + +4.3 Binder 通信中的代理模式 +我们已经解释清楚 Client、Server 借助 Binder 驱动完成跨进程通信的实现机制了,但是还有个问题会让我们困惑。A 进程想要 B 进程中某个对象(object)是如何实现的呢?毕竟它们分属不同的进程,A 进程 没法直接使用 B 进程中的 object。 + +前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。 + +当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。 + + + +4.4 Binder 的完整定义 +现在我们可以对 Binder 做个更加全面的定义了: + +从进程间通信的角度看,Binder 是一种进程间通信的机制; +从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象; +从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理 +从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。 +五. 手动编码实现跨进程调用 +通常我们在做开发时,实现进程间通信用的最多的就是 AIDL。当我们定义好 AIDL 文件,在编译时编译器会帮我们生成代码实现 IPC 通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。 + +但是无论是从可读性还是可理解性上来看,编译器生成的代码对开发者并不友好。比如一个 BookManager.aidl 文件对应会生成一个 BookManager.java 文件,这个 java 文件包含了一个 BookManager 接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类,Stub 又是 BookManager 的静态内部类,这就造成了可读性和可理解性的问题。 + +Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 BookManager、Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。 + +因此便于大家理解,下面我们来手动编写代码来实现跨进程调用。 + +5.1 各 Java 类职责描述 +在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。 + +IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。 + +IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口) + +Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。 + +Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。 + +5.2 实现过程讲解 +一次跨进程通信必然会涉及到两个进程,在这个例子中 RemoteService 作为服务端进程,提供服务;ClientActivity 作为客户端进程,使用 RemoteService 提供的服务。如下图: + + + +那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?还记得我们前面介绍过的 IInterface 吗,它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 BookManager 接口,BookManager 继承自 IIterface,表明服务端具备什么样的能力。 + +/** + * 这个类用来定义服务端 RemoteService 具备什么样的能力 + */ +public interface BookManager extends IInterface { + + void addBook(Book book) throws RemoteException; +} +只定义服务端具备什么要的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。 + +public abstract class Stub extends Binder implements BookManager { + + ... + + public static BookManager asInterface(IBinder binder) { + if (binder == null) + return null; + IInterface iin = binder.queryLocalInterface(DESCRIPTOR); + if (iin != null && iin instanceof BookManager) + return (BookManager) iin; + return new Proxy(binder); + } + + ... + + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { + switch (code) { + + case INTERFACE_TRANSACTION: + reply.writeString(DESCRIPTOR); + return true; + + case TRANSAVTION_addBook: + data.enforceInterface(DESCRIPTOR); + Book arg0 = null; + if (data.readInt() != 0) { + arg0 = Book.CREATOR.createFromParcel(data); + } + this.addBook(arg0); + reply.writeNoException(); + return true; + + } + return super.onTransact(code, data, reply, flags); + } + + ... +} +Stub 类中我们重点介绍下 asInterface 和 onTransact。 + +先说说 asInterface,当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 BookManager 对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。 + +接下来我们就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 BookManager 接口。 + +public class Proxy implements BookManager { + + ... + + public Proxy(IBinder remote) { + this.remote = remote; + } + + @Override + public void addBook(Book book) throws RemoteException { + + Parcel data = Parcel.obtain(); + Parcel replay = Parcel.obtain(); + try { + data.writeInterfaceToken(DESCRIPTOR); + if (book != null) { + data.writeInt(1); + book.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0); + replay.readException(); + } finally { + replay.recycle(); + data.recycle(); + } + } + + ... +} +我们看看 addBook() 的实现;在 Stub 类中,addBook(Book book) 是一个抽象方法,Client 端需要继承并实现它。 + +如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。 +如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。 +在 Proxy 中的 addBook() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 addBook() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 BookManager 接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 addBook() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。 + +这样一次跨进程调用就完成了。 + +完整的代码我放到 GitHub 上了,有兴趣的小伙伴可以去看看。源码地址:https://github.com/BaronZ88/HelloBinder + +最后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信,加深对 Binder 通信过程的理解。 + +受个人能力水平限制,文章中难免会有错误。如果大家发现文章不足之处,欢迎与我沟通交流。 + +本文在写作过程中参考了很多文章、书籍和源码,其中有很多描述和图片都借鉴了下面的文章,在这里感谢大佬们的无私分享! + + + + diff --git a/sources/binearyTree.md b/sources/binearyTree.md new file mode 100644 index 0000000..84888c4 --- /dev/null +++ b/sources/binearyTree.md @@ -0,0 +1,13 @@ +### 二叉树的性质 + + +若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点: + +(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;   + +(2) 若 2i>n,则该结点无左孩子,  否则,编号为 2i 的结点为其左孩子结点; + +(3) 若 2i+1>n,则该结点无右孩子结点,  否则,编号为2i+1 的结点为其右孩子结点。 + + +**注意 编号是从1开始才有这个关系,如果是从0开始就不满住这个关系,所以切记 \ No newline at end of file diff --git a/sources/blockcanary.md b/sources/blockcanary.md new file mode 100644 index 0000000..4557bca --- /dev/null +++ b/sources/blockcanary.md @@ -0,0 +1,185 @@ +BlockCanary原理分析 + +BlockCanary是国内开发者MarkZhai开发的一套性能监控组件,它对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析、定位到问题所在,迅速优化应用。 + +1.基本使用 +使用非常方便,引入 + +dependencies { + + compile 'com.github.markzhai:blockcanary-android:1.5.0' + + // 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用 + debugCompile 'com.github.markzhai:blockcanary-android:1.5.0' + releaseCompile 'com.github.markzhai:blockcanary-no-op:1.5.0' +} + + +在应用的application中完成初始化 + +public class DemoApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + BlockCanary.install(this, new AppContext()).start(); + } +} + +//参数设置 +public class AppContext extends BlockCanaryContext { + private static final String TAG = "AppContext"; + + @Override + public String provideQualifier() { + String qualifier = ""; + try { + PackageInfo info = DemoApplication.getAppContext().getPackageManager() + .getPackageInfo(DemoApplication.getAppContext().getPackageName(), 0); + qualifier += info.versionCode + "_" + info.versionName + "_YYB"; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "provideQualifier exception", e); + } + return qualifier; + } + + @Override + public int provideBlockThreshold() { + return 500; + } + + @Override + public boolean displayNotification() { + return BuildConfig.DEBUG; + } + + @Override + public boolean stopWhenDebugging() { + return false; + } +} + + + + +2、基本原理 +我们都知道Android应用程序只有一个主线程ActivityThread,这个主线程会创建一个Looper(Looper.prepare),而Looper又会关联一个MessageQueue,主线程Looper会在应用的生命周期内不断轮询(Looper.loop),从MessageQueue取出Message 更新UI。 +我们来看一个代码片段 + +public static void loop() { + ... + for (;;) { + ... + // This must be in a local variable, in case a UI event sets the logger + Printer logging = me.mLogging; + if (logging != null) { + logging.println(">>>>> Dispatching to " + msg.target + " " + + msg.callback + ": " + msg.what); + } + msg.target.dispatchMessage(msg); + if (logging != null) { + logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); + } + ... + } +} + + + +msg.target其实就是Handler,看一下dispatchMessage的逻辑 + +/** + * Handle system messages here. + */ +public void dispatchMessage(Message msg) { + if (msg.callback != null) { + handleCallback(msg); + } else { + if (mCallback != null) { + if (mCallback.handleMessage(msg)) { + return; + } + } + handleMessage(msg); + } +} + + +即调用BlockCanary的start方法 + +public void start() { + if (!mMonitorStarted) { + mMonitorStarted = true; + Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor); + } +} + +* 如果消息是通过Handler.post(runnable)方式投递到MQ中的,那么就回调runnable#run方法; +* 如果消息是通过Handler.sendMessage的方式投递到MQ中,那么回调handleMessage方法; +不管是哪种回调方式,回调一定发生在UI线程。因此如果应用发生卡顿,一定是在dispatchMessage中执行了耗时操作。我们通过给主线程的Looper设置一个Printer,打点统计dispatchMessage方法执行的时间,如果超出阀值,表示发生卡顿,则dump出各种信息,提供开发者分析性能瓶颈。 + +@Override +public void println(String x) { + if (!mStartedPrinting) { + mStartTimeMillis = System.currentTimeMillis(); + mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis(); + mStartedPrinting = true; + startDump(); + } else { + final long endTime = System.currentTimeMillis(); + mStartedPrinting = false; + if (isBlock(endTime)) { + notifyBlockEvent(endTime); + } + stopDump(); + } +} + +private boolean isBlock(long endTime) { + return endTime - mStartTimeMillis > mBlockThresholdMillis; +} + + +private void startDump() { +//收集栈信息 + if (null != BlockCanaryCore.get().threadStackSampler) { + BlockCanaryCore.get().threadStackSampler.start(); + } + + if (null != BlockCanaryCore.get().cpuSampler) { + BlockCanaryCore.get().cpuSampler.start(); + } +} + +private void stopDump() { + if (null != BlockCanaryCore.get().threadStackSampler) { + BlockCanaryCore.get().threadStackSampler.stop(); + } + + if (null != BlockCanaryCore.get().cpuSampler) { + BlockCanaryCore.get().cpuSampler.stop(); + } +} +//具体收集算法 + + protected void doSample() { +// Log.d("BlockCanary", "sample thread stack: [" + mThreadStackEntries.size() + ", " + mMaxEntryCount + "]"); + StringBuilder stringBuilder = new StringBuilder(); + + // Fetch thread stack info + for (StackTraceElement stackTraceElement : mThread.getStackTrace()) { + stringBuilder.append(stackTraceElement.toString()) + .append(Block.SEPARATOR); + } + + // Eliminate obsolete entry + synchronized (mThreadStackEntries) { + if (mThreadStackEntries.size() == mMaxEntryCount && mMaxEntryCount > 0) { + mThreadStackEntries.remove(mThreadStackEntries.keySet().iterator().next()); + } + mThreadStackEntries.put(System.currentTimeMillis(), stringBuilder.toString()); + } + } + + + diff --git a/sources/blockqueue.md b/sources/blockqueue.md new file mode 100644 index 0000000..05db2b6 --- /dev/null +++ b/sources/blockqueue.md @@ -0,0 +1,86 @@ +### BlockingQueue + + +1. 首先 他是在Concurrent包中,BlockingQueue很好的解决了多线程中如何高效安全“传输”数据的问题,通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利 + +2. 认识 BlockingQueue + + 他是一个队列 在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。好在此时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。(在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒) + + + - 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列 + - 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。 + + 这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。既然BlockingQueue如此神通广大,让我们一起来见识下它的常用方法: + + 1.放入数据 + +     (1)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法 + + 的线程);       +   (2)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。 + +     (3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续. + +   2. 获取数据 + +     (1)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null; + +     (2)poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间 + + 超时还没有数据可取,返回失败。 + +     (3)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入; + +     (4)drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。 + + +### 常见BlockingQueue + +1. ArrayBlockingQueue + +  基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。 + +  ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。 + +2. LinkedBlockingQueue + +  基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。 + +  作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。 + +  ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。 + +3. DelayQueue + +  DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。 + +  使用场景: + +  DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。 + +4. PriorityBlockingQueue + +   基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。 + +5. SynchronousQueue + +   一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。 + +  声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别: + +  如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略; + +  但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。 + + + +#### BlockingDeque 又是什么?? + + java6增加了两种容器类型,Deque和BlockingDeque,它们分别对Queue和BlockingQueue进行了扩展。 +   Deque是一个双端队列,deque(双端队列) 是 "Double Ended Queue" 的缩写。因此,双端队列是一个你可以从任意一端插入或者抽取元素的队列。实现了在队列头和队列尾的高效插入和移除。 +   BlockingDeque 类是一个双端队列,在不能够插入元素时,它将阻塞住试图插入元素的线程;在不能够抽取元素时,它将阻塞住试图抽取的线程。 + + + + diff --git a/sources/butterknife.md b/sources/butterknife.md new file mode 100644 index 0000000..1c37b32 --- /dev/null +++ b/sources/butterknife.md @@ -0,0 +1,103 @@ +ButterKnife源码分析 + + + + 1. 编译器干了什么 + + 扫描注解,获取所有带有注解的文件, 生成一个className_ViewBinding.java的文件 ;例如MainActivity使用注解后生成 + + MainActivity_ViewBinding.java + + + public class MainActivity_ViewBinding implements Unbinder { + private MainActivity target; + + @UiThread + public MainActivity_ViewBinding(MainActivity target) { + this(target, target.getWindow().getDecorView()); + } + + @UiThread + public MainActivity_ViewBinding(MainActivity target, View source) { + this.target = target; + + target.btn_test = Utils.findRequiredViewAsType(source, R.id.btn_test, "field 'btn_test'", Button.class); + } + + @Override + @CallSuper + public void unbind() { + MainActivity target = this.target; + if (target == null) throw new IllegalStateException("Bindings already cleared."); + this.target = null; + + target.btn_test = null; + } + } + + +2. Butterknife.bind() 干了什么? + + @NonNull @UiThread + public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) { + View sourceView = source.getWindow().getDecorView(); + return createBinding(target, sourceView); + } + + private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { + Class targetClass = target.getClass(); + if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); + Constructor constructor = findBindingConstructorForClass(targetClass); + + if (constructor == null) { + return Unbinder.EMPTY; + } + + //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. + try { + return constructor.newInstance(target, source); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to invoke " + constructor, e); + } catch (InstantiationException e) { + throw new RuntimeException("Unable to invoke " + constructor, e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw new RuntimeException("Unable to create binding instance.", cause); + } + } + + @Nullable @CheckResult @UiThread + private static Constructor findBindingConstructorForClass(Class cls) { + Constructor bindingCtor = BINDINGS.get(cls); + if (bindingCtor != null) { + if (debug) Log.d(TAG, "HIT: Cached in binding map."); + return bindingCtor; + } + String clsName = cls.getName(); + if (clsName.startsWith("android.") || clsName.startsWith("java.")) { + if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); + return null; + } + try { + Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); + //noinspection unchecked + bindingCtor = (Constructor) bindingClass.getConstructor(cls, View.class); + if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor."); + } catch (ClassNotFoundException e) { + if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); + bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Unable to find binding constructor for " + clsName, e); + } + BINDINGS.put(cls, bindingCtor); + return bindingCtor; + } + + +通过对象获取class,通过class查询缓存 看看有没有,如果没有,就用classLoder加载一个 clsName_ViewBinding类文件,这个就是刚刚生成,然后拿到构造方法,传入布局和对象,创建clsName_ViewBinding 对象,而clsName_ViewBinding的构造方法就是进行findViewById了 \ No newline at end of file diff --git a/sources/concurrentHashMap.md b/sources/concurrentHashMap.md new file mode 100644 index 0000000..3b03a57 --- /dev/null +++ b/sources/concurrentHashMap.md @@ -0,0 +1,40 @@ +### JDK 1.5 1.8 ConcurrentHashMap 源码剖析 + + +#### ConcurrentHashMap 是一个并发散列映射表的实现,它允许完全并发的读取,并且支持给定数量的并发更新。相比于 HashTable 和同步包装器包装的 HashMap,使用一个全局的锁来同步不同线程间的并发访问,同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器,这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成串行化的了。 + +1.6中采用ReentrantLock 分段锁的方式,使多个线程在不同的segment上进行写操作不会发现阻塞行为; + +1.8中直接采用了内置锁synchronized,难道是因为1.8的虚拟机对内置锁已经优化的足够快了? + + +1.6 分段锁技术,细粒度划分锁,每一个数据一把锁 + + public class MyConcurrentHashMap { + private final int LOCK_COUNT = 16; + private final Map map; + private final Object[] locks ; + + public MyConcurrentHashMap() { + this.map = new HashMap(); + locks = new Object[LOCK_COUNT]; + for (int i=0;i(5) + var putptr: Int = 0 + var takeptr: Int = 0 + var count: Int = 0 + + @Throws(InterruptedException::class) + fun put(x: Any) { + lock.lock() //获取锁 + try { + // 如果“缓冲已满”,则等待;直到“缓冲”不是满的,才将x添加到缓冲中。 + while (count == items.size) + notFull.await() + // 将x添加到缓冲中 + items[putptr] = x + // 将“put统计数putptr+1”;如果“缓冲已满”,则设putptr为0。 + if (++putptr == items.size) putptr = 0 + // 将“缓冲”数量+1 + ++count + // 唤醒take线程,因为take线程通过notEmpty.await()等待 + notEmpty.signal() + + // 打印写入的数据 + println(Thread.currentThread().name + " put " + x as Int) + } finally { + lock.unlock() // 释放锁 + } + } + + @Throws(InterruptedException::class) + fun take(): Any { + lock.lock() //获取锁 + try { + // 如果“缓冲为空”,则等待;直到“缓冲”不为空,才将x从缓冲中取出。 + while (count == 0) + notEmpty.await() + // 将x从缓冲中取出 + val x = items[takeptr] + // 将“take统计数takeptr+1”;如果“缓冲为空”,则设takeptr为0。 + if (++takeptr == items.size) takeptr = 0 + // 将“缓冲”数量-1 + --count + // 唤醒put线程,因为put线程通过notFull.await()等待 + notFull.signal() + + // 打印取出的数据 + println(Thread.currentThread().name + " take " + x as Int) + return x + } finally { + lock.unlock() // 释放锁 + } + } + } diff --git a/sources/copyOnWriteArrayList.md b/sources/copyOnWriteArrayList.md new file mode 100644 index 0000000..40c59b8 --- /dev/null +++ b/sources/copyOnWriteArrayList.md @@ -0,0 +1,162 @@ +### CopyOnWirteArrayList 原理 + + +面试官问:“ArrayList是线程安全的吗?如果ArrayList线程不安全的话,那有没有安全的类似ArrayList的容器” + +applicant:“线程安全的ArrayList我们可以使用Vector,或者说我们可以使用Collections下的方法来包装一下” + +面试官继续问:“嗯,我相信你也知道Vector是一个比较老的容器了,还有没有其他的呢?” + +如果你 emmmmmmm + +面试官:”ok,ok,ok,今天的面试时间也差不多了,你回去等通知吧。“ + +以上基本就没戏了 + + +1. Vector确实是线程安全的,但是效率极低,几乎所有方法都加了锁 + +2. Collections.synchronizedList(new ArrayList()) 也可以确保线程安全,也是几乎都是每个方法都加上synchronized关键字的,只不过它不是加在方法的声明处,而是方法的内部 + + + public static List synchronizedList(List list) { + return (list instanceof RandomAccess ? + new SynchronizedRandomAccessList<>(list) : + new SynchronizedList<>(list)); + } + + + + 调用 Collections.synchronizedList()会返回一个SynchronizedList + + + + + static class SynchronizedList + extends SynchronizedCollection + implements List { + private static final long serialVersionUID = -7754090372962971524L; + + final List list; + + SynchronizedList(List list) { + super(list); + this.list = list; + } + SynchronizedList(List list, Object mutex) { + super(list, mutex); + this.list = list; + } + + public boolean equals(Object o) { + if (this == o) + return true; + synchronized (mutex) {return list.equals(o);} + } + public int hashCode() { + synchronized (mutex) {return list.hashCode();} + } + + public E get(int index) { + synchronized (mutex) {return list.get(index);} + } + public E set(int index, E element) { + synchronized (mutex) {return list.set(index, element);} + } + public void add(int index, E element) { + synchronized (mutex) {list.add(index, element);} + } + public E remove(int index) { + synchronized (mutex) {return list.remove(index);} + } + + public int indexOf(Object o) { + synchronized (mutex) {return list.indexOf(o);} + } + public int lastIndexOf(Object o) { + synchronized (mutex) {return list.lastIndexOf(o);} + } + + public boolean addAll(int index, Collection c) { + synchronized (mutex) {return list.addAll(index, c);} + } + + public ListIterator listIterator() { + return list.listIterator(); // Must be manually synched by user + } + + public ListIterator listIterator(int index) { + return list.listIterator(index); // Must be manually synched by user + } + + public List subList(int fromIndex, int toIndex) { + synchronized (mutex) { + return new SynchronizedList<>(list.subList(fromIndex, toIndex), + mutex); + } + } + + @Override + public void replaceAll(UnaryOperator operator) { + synchronized (mutex) {list.replaceAll(operator);} + } + @Override + public void sort(Comparator c) { + synchronized (mutex) {list.sort(c);} + } + + /** + * SynchronizedRandomAccessList instances are serialized as + * SynchronizedList instances to allow them to be deserialized + * in pre-1.4 JREs (which do not have SynchronizedRandomAccessList). + * This method inverts the transformation. As a beneficial + * side-effect, it also grafts the RandomAccess marker onto + * SynchronizedList instances that were serialized in pre-1.4 JREs. + * + * Note: Unfortunately, SynchronizedRandomAccessList instances + * serialized in 1.4.1 and deserialized in 1.4 will become + * SynchronizedList instances, as this method was missing in 1.4. + */ + private Object readResolve() { + return (list instanceof RandomAccess + ? new SynchronizedRandomAccessList<>(list) + : this); + } + } + + + + SynchronizedList 代理了ArrayList的所有方法,每个方法也都加入了synchronized 同步锁,所以效率也是低的 + +3. CopyOnWriteArrayList 源码分析 + + + public CopyOnWriteArrayList() { + setArray(new Object[0]); + } + + public boolean add(E e) { + synchronized (lock) { + Object[] elements = getArray(); + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len + 1); + newElements[len] = e; + setArray(newElements); + return true; + } + } + + + 如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。 + +4. 相比Vector和SynchronizedList 遍历都加锁效率会很低,CopyOnWriteArrayList 是 只有写的时候加锁,读的时候不去加锁,这样读的时候会读老集合, + + + 内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行add()、set()、remove()的话,那是比较耗费内存的。 + + 因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。 + 数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。 + + 从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。 + +5. CopyOnWriteArrayList 适用于读多,写少, 数据集合小的 \ No newline at end of file diff --git a/sources/davik_art.md b/sources/davik_art.md new file mode 100644 index 0000000..4d31975 --- /dev/null +++ b/sources/davik_art.md @@ -0,0 +1,10 @@ +### Android两种虚拟机区别和联系 + + +1. Android 从5.0开始默认使用ART虚拟机执行程序,抛弃了Dalvik虚拟机.加快了Android的运行效率,提高系统的流畅性 + +原因是Dalvik虚拟机执行的是dex字节码,ART虚拟机执行的是本地机器码, Dalvik虚拟机有一个解释器,用来执行dex字节码, Android从2.2开始,通过JIT(Just-In-Time)进行Dalvik虚拟机的优化,将使用频率较高的字节码翻译成机器码,就可以有效地提高Dalvik虚拟机的执行效率。但即使用采用了JIT,Dalvik虚拟机还是比不上ART虚拟机,因为Dalvik翻译工作是在程序运行时的,而ART在APK在安装时就对其包含的Dex字节码进行翻译,得到对应的本地机器指令,于是就可以在运行时直接执行了。 + + + +2. Android系统通过PackageManagerService来安装APK,在安装的过程,PackageManagerService会通过另外一个类Installer的成员函数dexopt来对APK里面的dex字节码进行优化,对Dalvik虚拟机来说只进行dex字节码的优化,而ART虚拟机将dex字节码翻译成本地机器码,注意的是两种虚拟机不管事字节码的优化还是翻译成机器码都会生成一个后缀是odex文件,只不过ART的是一个oat类型文件,什么是oat文件(不清楚,好像是Linux的文件) \ No newline at end of file diff --git a/sources/design_v28.md b/sources/design_v28.md new file mode 100644 index 0000000..409039d --- /dev/null +++ b/sources/design_v28.md @@ -0,0 +1,3 @@ +### Android Design Support V28 新增加内容 + +1. \ No newline at end of file diff --git a/sources/designpattern/abstractfactory.md b/sources/designpattern/abstractfactory.md new file mode 100644 index 0000000..a521726 --- /dev/null +++ b/sources/designpattern/abstractfactory.md @@ -0,0 +1,90 @@ +### 抽象工厂 + + +//抽象工厂类,电脑工厂类 + + + + public abstract class ComputerFactory { + public abstract CPU createCPU(); + + public abstract Memory createMemory(); + + public abstract HD createHD(); + } + + + + + public class LenovoComputerFactory extends ComputerFactory { + + @Override + public CPU createCPU() { + return new IntelCPU(); + } + + @Override + public Memory createMemory() { + return new SamsungMemory(); + } + + @Override + public HD createHD() { + return new SeagateHD(); + } + } + + //具体工厂类--华硕电脑 + public class AsusComputerFactory extends ComputerFactory { + + @Override + public CPU createCPU() { + return new AmdCPU(); + } + + @Override + public Memory createMemory() { + return new KingstonMemory(); + } + + @Override + public HD createHD() { + return new WdHD(); + } + } + + //具体工厂类--惠普电脑 + public class HpComputerFactory extends ComputerFactory { + + @Override + public CPU createCPU() { + return new IntelCPU(); + } + + @Override + public Memory createMemory() { + return new KingstonMemory(); + } + + @Override + public HD createHD() { + return new WdHD(); + } + } + + +生产多个产品组合的对象时。 + +优点 + +代码解耦,创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建。 + +缺点 + +如果增加新的产品,则修改抽象工厂和所有的具体工厂,违反了开放封闭原则 + + +工厂方法模式与抽象工厂模式比较 + +在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法具有唯一性。 +抽象工厂模式则可以提供多个产品对象,而不是单一的产品对象。 diff --git a/sources/designpattern/adapter.md b/sources/designpattern/adapter.md new file mode 100644 index 0000000..ee05339 --- /dev/null +++ b/sources/designpattern/adapter.md @@ -0,0 +1,39 @@ +### 适配器模式 + + + +- 对象适配器模式与类适配器模式比较 + + + 类适配器采用了继承的方式来实现;而对象适配器是通过传递对象来实现,这是一种组合的方式。 + 类适配器由于采用了继承,可以重写父类的方法;对象适配器则不能修改对象本身的方法等。 + 适配器通过继承都获得了父类的方法,客户端使用时都会把这些方法暴露出去,增加了一定的使用成本;对象适配器则不会。 + 类适配器只能适配他的父类,这个父类的其他子类都不能适配到;而对象适配器可以适配不同的对象,只要这个对象的类型是同样的。 + 类适配器不需要额外的引用;对象适配器需要额外的引用来保存对象。 + + 总的来说,使用对象适配器比较好。当然具体问题具体分析。 + + +- 应用场景 + + + 当想使用一个已经存在的类,但它的接口不符合需求时。 + 当想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。 + +- 优点 + + + 提高了类的复用性,适配器能让一个类有更广泛的用途。 + 提高了灵活性,更换适配器就能达到不同的效果。不用时也可以随时删掉适配器,对原系统没影响。 + 符合开放封闭原则,不用修改原有代码。没有任何关系的类通过增加适配器就能联系在一起。 + +- 缺点 + + + 过多的使用适配器,会让系统非常零乱,不易整体进行把握。明明调用A接口,却被适配成B接口 + + +- Android中的源码分析 + + + 说到适配器,ListView和RecyclerView就再熟悉不过了,ListView现在应该很少用了吧,这里就以RecyclerView来分析 \ No newline at end of file diff --git a/sources/designpattern/adapterdiffdecorator.md b/sources/designpattern/adapterdiffdecorator.md new file mode 100644 index 0000000..621ab0c --- /dev/null +++ b/sources/designpattern/adapterdiffdecorator.md @@ -0,0 +1,16 @@ + +### 适配器模式与装饰器模式和代理模式区别区别 + +装饰器与适配器都有一个别名叫做 包装模式(Wrapper),它们看似都是起到包装一个类或对象的作用,但是使用它们的目的很不一一样。 + +适配器模式的意义是要将一个接口转变成另一个接口,它的目的是通过改变接口来达到重复使用的目的。 + +而装饰器模式不是要改变被装饰对象的接口,而是恰恰要保持原有的接口,但是增强原有对象的功能,或者改变原有对象的处理方式而提升性能。所以这两个模式设计的目的是不同的。 + + +** 代理提供的接口和原本的要实现统一接口,代理模式的作用是不把实现直接暴露给client,而是通过代理这个层,代理能够做一些处理,判断。** + +** 适配器模式体现的是适配,比如Client 需要A类提供的行为,此时我们有B类提供了一些方法可以实现,但是方法名字不一样,需要改造一下变成A, + 此时创建一个类C,实现A接口,并注入B类,这样相当于C就通过协调用B的方法,来补充到A接口方法中(所以这里C属于适配器,来协调Client 和 B) + 对适配器模式的功能很好理解,就是把一个类的接口变换成客户端所能接受的另一种接口 + \ No newline at end of file diff --git a/sources/designpattern/bridge.md b/sources/designpattern/bridge.md new file mode 100644 index 0000000..3ae4e90 --- /dev/null +++ b/sources/designpattern/bridge.md @@ -0,0 +1,38 @@ +### 桥接设计模式 + +1. 介绍 + +桥接模式属于结构型模式。 +举个生活中的例子,一条数据线,一头USB接口的可以连接电脑、充电宝等等,另一头可以连接不同品牌的手机,通过这条数据线,两头不同的东西就可以连接起来,这就是桥接模式 + +2. 应用场景 + +一个类存在两个或以上的独立维度的变化,且这些维度都需要进行拓展。 +不希望使用继承或因为多层次继承导致类的个数急剧增加时。 +如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,可以通过桥接模式使他们在抽象层建立一个关联关系。 + +3. 优点 + +分离了抽象与实现。让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统。 +良好的扩展性。抽象部分和实现部分都可以分别独立扩展,不会相互影响。 + +4. 缺点 + +增加了系统的复杂性。 +不容易设计,抽象与实现的分离要设计得好比较有难度 + + - Android中的源码分析 + + 桥接模式在Android中的源码应用还是非常广泛的。比如AbsListView跟ListAdapter之间就是一个桥接模式。 + + + - AbsListView 必须是一个抽象或接口; ListAdapter也必须是一个抽象或接口 + + - AbsListView 依赖于 ListAdaper 一个引用 + + - ListView GridView 都继承了AbsListView ; ArrayAdapter SimpleAdapter 都继承了ListAdapter + + - ListView GridView 和 ArrayAdapter SimpleAdapter 之间的桥梁 就是AbsListView 和ListAdapter + + - + \ No newline at end of file diff --git a/sources/designpattern/builder.md b/sources/designpattern/builder.md new file mode 100644 index 0000000..6803a3c --- /dev/null +++ b/sources/designpattern/builder.md @@ -0,0 +1,20 @@ +### 建造者模式 + +1. 建造者模式属于创建型模式。 + 建造者模式主要用来创建复杂的对象,用户可以不用关心其建造过程和细节。 + +2. 优点 + + + 封装性良好,隐藏内部构建细节。 + 易于解耦,将产品本身与产品创建过程进行解耦,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。 + 易于扩展,具体的建造者类之间相互独立,增加新的具体建造者无需修改原有类库的代码。 + 易于精确控制对象的创建,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。 + +7.缺点 + + 产生多余的Build对象以及Dirextor类。 + 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。 + 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 + +2. Android 中 AlertDialog, diff --git a/sources/designpattern/chainofResponsibility.md b/sources/designpattern/chainofResponsibility.md new file mode 100644 index 0000000..fdef704 --- /dev/null +++ b/sources/designpattern/chainofResponsibility.md @@ -0,0 +1,106 @@ +### 责任链模式 + +责任链模式是对一个事件的处理方法,所有能对事件进行处理的对象按顺序形成一个链表.事件经过链表中每个处理对象轮流处理.如果有返回值.则返回也是顺着这条链表反向返回.这个链表是先进后出模式. + + +Android中的源码分析 + + Android中的事件分发机制就是类似于责任链模式,关于事件分发机制 + + 根View 将事件传递给 子View 子View 再传递给子View ;子View 开始处理,没出处理就返回上一级去处理,递归是回溯调用,是责任链实现的一种方式 + + 另外,OKhttp中对请求的处理也是用到了责任链模式,有兴趣的可以去看下OKhttp的源码。后面有时间也会对OKhttp的源码进行分析。 + + +1. 定义责任链的抽象 + + + public interface Interceptor { + Response intercept(Chain chain) throws IOException; + + interface Chain { + Request request(); + + Response proceed(Request request) throws IOException; + + /** + * Returns the connection the request will be executed on. This is only available in the chains + * of network interceptors; for application interceptors this is always null. + */ + @Nullable Connection connection(); + } + } + + + + +OkHttp 中责任链实现 + + + + + + interface Intercept { + + fun intercept(chain: Chain):Response? + } + + class Chain(val index: Int, val intercepts: List,val request: Request) { + + fun procced(index: Int, intercepts: List, request: Request):Response? { + if (index < intercepts.size) { + val intercept = intercepts.get(index) + val next = Chain(index+1,intercepts,request) + val response = intercept.intercept(next) + return response + } + + return null + } + + fun procced():Response?{ + return procced(index,intercepts,request) + } + } + + class Response + class Request(var url:String) + + + + fun main() { + + val intercepts = arrayListOf() + + intercepts.add(object:Intercept{ + override fun intercept(chain: Chain): Response? { + chain.request.url = "123" + // 这里不会立即返回,需要等最后一个拦截器执行完,这是一个递归的操作,也可以直接 return null 或者想要的数据 + return chain.procced() + } + + }) + //添加很多拦截器 + val request = Request("hahahah") + val chain = Chain(0, intercepts, request) + chain.procced(0, intercepts, request) + } + + + + 其中可以优化一下 + + abstract class Intercept { + + open fun intercept(chain: Chain):Response?{ + return chain.procced() + } + } + + class MyIntercept: Intercept() { + override fun intercept(chain: Chain): Response? { + return super.intercept(chain) + } + } + + 这种方式就可以在super前后做修改 \ No newline at end of file diff --git a/sources/designpattern/commandPattern.md b/sources/designpattern/commandPattern.md new file mode 100644 index 0000000..488d0b6 --- /dev/null +++ b/sources/designpattern/commandPattern.md @@ -0,0 +1,71 @@ +### 命令模式 + +Command(命令角色):接口或者抽象类,定义要执行的命令。 + +ConcreteCommand(具体命令角色):命令角色的具体实现,通常会持有接收者,并调用接收者来处理命令。 + +Invoker(调用者角色):负责调用命令对象执行请求,通常会持有命令对象(可以持有多个命令对象)。Invoker是Client真正触发命令并要求命令执行相应操作的地方(使用命令对象的入口)。 + +Receiver(接收者角色):是真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。 + +Client(客户端角色):Client可以创建具体的命令对象,并且设置命令对象的接收者。 + + + Client + + public void test() { + Receiver receiver = new Receiver();//创建命令接收者 + Command command = new ShutdownCommand(receiver);//创建一个命令的具体实现对象,并指定命令接收者 + Invoker invoker = new Invoker(command);//创建一个命令调用者,并指定具体命令 + invoker.action();//发起调用命令请求 + } + + +1. 命令模式同时也支持命令的撤销(Undo)操作和恢复(Redo)操作,比如我们平时关机时,也是可以撤销关机的。至于恢复操作,需要我们记下执行过的命令,在需要的时候重新执行一遍。 + +2. 优点 + + 调用者与接受者之间的解藕。 + 易于扩展,扩展命令只需新增具体命令类即可,符合开放封闭原则。 + +3. 缺点 + + 过多的命令会造成过多的类, + + +4. Android 中Thread + + + 实际上Thread的使用就是一个简单的命令模式,先看下Thread的使用: + + new Thread(new Runnable() { + @Override + public void run() { + //doSomeThing + } + }).start(); + + + Thread的start()方法即命令的调用者, + + + @Override + public void run() { + if (target != null) { + target.run(); + } + } + + 同时Thread的内部会调用Runnable的run(),这里Thread又充当了具体的命令角色, + + 最后的Runnable则是接受者了,负责最后的功能处理。 + + +5. Android 中Handler + + + 另一个比较典型的常用到命令模式就是Handler了,这里就不贴代码了,简单分析下各个类的角色: + + 接受者:Handler,执行消息的处理操作。 + 调用者:Looper,调用消息的的处理方法。 + 命令角色:Message,消息类。 \ No newline at end of file diff --git a/sources/designpattern/composite.md b/sources/designpattern/composite.md new file mode 100644 index 0000000..0c4bede --- /dev/null +++ b/sources/designpattern/composite.md @@ -0,0 +1,208 @@ +### 组合模式 + + +1. 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 + +2. 介绍 + + 组合模式属于结构型模式。 + + 组合模式有时叫做部分—整体模式,主要是描述部分与整体的关系。 + + 组合模式实际上就是个树形结构,一棵树的节点如果没有分支,就是叶子节点;如果存在分支,则是树枝节点。 + + 我们平时遇到的最典型的组合结构就是文件和文件夹了,具体的文件就是叶子节点,而文件夹下还可以存在文件和文件夹,所以文件夹一般是树枝节点 + + + +3. 例子 + + + + 透明的组合模式 + public class Content extends PageElement {//具体内容 + + public Content(String name) { + super(name); + } + + @Override + public void addPageElement(PageElement pageElement) { + throw new UnsupportedOperationException("不支持此操作"); + } + + @Override + public void rmPageElement(PageElement pageElement) { + throw new UnsupportedOperationException("不支持此操作"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("不支持此操作"); + } + + @Override + public void print(String placeholder) { + System.out.println(placeholder + "──" + getName()); + } + + } + + public class Column extends PageElement {//栏目 + + public Column(String name) { + super(name); + } + + @Override + public void addPageElement(PageElement pageElement) { + mPageElements.add(pageElement); + } + + @Override + public void rmPageElement(PageElement pageElement) { + mPageElements.remove(pageElement); + } + + @Override + public void clear() { + mPageElements.clear(); + } + + /** + * @param placeholder 占位符 + */ + @Override + public void print(String placeholder) { + //利用递归来打印文件夹结构 + System.out.println(placeholder + "└──" + getName()); + Iterator i = mPageElements.iterator(); + while (i.hasNext()) { + PageElement pageElement = i.next(); + pageElement.print(placeholder + " "); + } + } + + } + + + public void test() { + //创建网站根页面 root + PageElement root = new Column("网站页面"); + //网站页面添加两个栏目:音乐,视屏;以及一个广告内容。 + PageElement music = new Column("音乐"); + PageElement video = new Column("视屏"); + PageElement ad = new Content("广告"); + root.addPageElement(music); + root.addPageElement(video); + root.addPageElement(ad); + + //音乐栏目添加两个子栏目:国语,粤语 + PageElement chineseMusic = new Column("国语"); + PageElement cantoneseMusic = new Column("粤语"); + music.addPageElement(chineseMusic); + music.addPageElement(cantoneseMusic); + + //国语,粤语栏目添加具体内容 + chineseMusic.addPageElement(new Content("十年.mp3")); + cantoneseMusic.addPageElement(new Content("明年今日.mp3")); + + //视频栏目添加具体内容 + video.addPageElement(new Content("唐伯虎点秋香.avi")); + + //打印整个页面的内容 + root.print(""); + } + + + + + 安全的组合模式 + + + public abstract class PageElement {//页面 + private String name; + + public PageElement(String name) { + this.name = name; + } + + //抽象组件角色去掉增删等接口 + + public abstract void print(String placeholder); + + public String getName() { + return name; + } + } + + public class Content extends PageElement {//具体内容,只专注自己的职责 + + public Content(String name) { + super(name); + } + + @Override + public void print(String placeholder) { + System.out.println(placeholder + "──" + getName()); + } + } + + public class Column extends PageElement {//栏目 + private List mPageElements = new ArrayList<>();//用来保存页面元素 + + public Column(String name) { + super(name); + } + + public void addPageElement(PageElement pageElement) { + mPageElements.add(pageElement); + } + + public void rmPageElement(PageElement pageElement) { + mPageElements.remove(pageElement); + } + + public void clear() { + mPageElements.clear(); + } + + @Override + public void print(String placeholder) { + System.out.println(placeholder + "└──" + getName()); + Iterator i = mPageElements.iterator(); + while (i.hasNext()) { + PageElement pageElement = i.next(); + pageElement.print(placeholder + " "); + } + } + + } + + public void test() {//客户端测试方法 + //依赖具体的实现类Column + Column root = new Column("网站页面"); + + Column music = new Column("音乐"); + Column video = new Column("视屏"); + PageElement ad = new Content("广告"); + root.addPageElement(music); + root.addPageElement(video); + root.addPageElement(ad); + + Column chineseMusic = new Column("国语"); + Column cantoneseMusic = new Column("粤语"); + music.addPageElement(chineseMusic); + music.addPageElement(cantoneseMusic); + + chineseMusic.addPageElement(new Content("十年.mp3")); + cantoneseMusic.addPageElement(new Content("明年今日.mp3")); + + video.addPageElement(new Content("唐伯虎点秋香.avi")); + + root.print(""); + } + + +安全的组合模式将职责区分开来放在不同的接口中,这样一来,设计上就比较安全,也遵循了单一职责原则和接口隔离原则,但是也让客户端必须依赖于具体的实现;透明的组合模式,以违反单一职责原则和接口隔离原则来换取透明性,但遵循依赖倒置原则,客户端可以直接依赖于抽象组件即可,将叶子和树枝一视同仁,也就是说,一个元素究竟是枝干节点还是叶子节点,对客户端是透明的。 +  一方面,我们写代码时应该遵循各种设计原则,但实际上,有些设计模式原则在使用时会发生冲突,这就需要我们根据实际情况去衡量做出取舍,适合自己的才是最好的。 diff --git a/sources/designpattern/decorator.md b/sources/designpattern/decorator.md new file mode 100644 index 0000000..167eaed --- /dev/null +++ b/sources/designpattern/decorator.md @@ -0,0 +1,122 @@ +### 装饰者模式 + + +- 动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。 + +Component(抽象组件):接口或者抽象类,被装饰的最原始的对象。具体组件与抽象装饰角色的父类。 +ConcreteComponent(具体组件):实现抽象组件的接口。 +Decorator(抽象装饰角色):一般是抽象类,抽象组件的子类,同时持有一个被装饰者的引用,用来调用被装饰者的方法;同时可以给被装饰者增加新的职责。 +ConcreteDecorator(具体装饰类):抽象装饰角色的具体实现。 + + +装饰者模式属于结构型模式。 +装饰者模式在生活中应用实际上也非常广泛,一如一间房,放上厨具,它就是厨房;放上床,就是卧室。 +通常我们扩展类的功能是通过继承的方式来实现,但是装饰者模式是通过组合的方式来实现,这是继承的替代方案之一。 + + + +- 应用场景 + + + 需要扩展一个类的功能,或给一个类增加附加功能时 + 需要动态的给一个对象增加功能,这些功能可以再动态的撤销 + 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。 + + +- 优点 + + + 采用组合的方式,可以动态的扩展功能,同时也可以在运行时选择不同的装饰器,来实现不同的功能。 + 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。 + 被装饰者与装饰者解偶,被装饰者可以不知道装饰者的存在,同时新增功能时原有代码也无需改变,符合开放封闭原则。 + +- 缺点 + + + 装饰层过多的话,维护起来比较困难。 + 如果要修改抽象组件这个基类的话,后面的一些子类可能也需跟着修改,较容易出错 + + + +Android中的源码分析 +我们都知道Activity、Service、Application等都是一个Context,这里面实际上就是通过装饰者模式来实现的。下面以startActivity()这个方法来简单分析一下。 + +Context类 + +Context实际上是个抽象类,里面定义了大量的抽象方法,其中就包含了startActivity()方法: + + +Context类在这里就充当了抽象组件的角色,ContextImpl类则是具体的组件,而ContextWrapper就是具体的装饰角色,通过扩展ContextWrapper增加不同的功能,就形成了Activity、Service等子类。 + + + - 总结:代码结构上更像一个链表的结构, 装饰器持有被装饰的引用,同时也有被装饰的同样方法,方法内部在对被装饰者调用 + + + public abstract class Room { + public abstract void fitment();//装修方法 + } + + public class NewRoom extends Room {//继承Room + @Override + public void fitment() { + System.out.println("这是一间新房:装上电"); + } + } + + + public abstract class RoomDecorator extends Room {//继承Room,拥有父类相同的方法 + private Room mRoom;//持有被装饰者的引用,这里是需要装修的房间 + + public RoomDecorator(Room room) { + this.mRoom = room; + } + + @Override + public void fitment() { + mRoom.fitment();//调用被装饰者的方法 + } + } + + + + public class Bedroom extends RoomDecorator {//卧室类,继承自RoomDecorator + + public Bedroom(Room room) { + super(room); + } + + @Override + public void fitment() { + super.fitment(); + addBedding(); + } + + private void addBedding() { + System.out.println("装修成卧室:添加卧具"); + } + } + + public class Kitchen extends RoomDecorator {//厨房类,继承自RoomDecorator + + public Kitchen(Room room) { + super(room); + } + + @Override + public void fitment() { + super.fitment(); + addKitchenware(); + } + + private void addKitchenware() { + System.out.println("装修成厨房:添加厨具"); + } + } + + public void test() { + Room newRoom = new NewRoom();//有一间新房间 + RoomDecorator bedroom = new Bedroom(newRoom); + bedroom.fitment();//装修成卧室 + RoomDecorator kitchen = new Kitchen(newRoom); + kitchen.fitment();//装修成厨房 + } diff --git a/sources/designpattern/delegation.md b/sources/designpattern/delegation.md new file mode 100644 index 0000000..2e36cc7 --- /dev/null +++ b/sources/designpattern/delegation.md @@ -0,0 +1,321 @@ +### 委托模式 + + +- 在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。 +委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式 本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承 + + + /** + * 委托者接口 + * */ + public interface Subject { + /** + * 添加被委托对象 + * @param obj:被委托对象 + */ + void addObserver(Observer obj); + /** + * 移除所有对象 + */ + void removeAll(); + /** + * 委托的事件 + * @param s:委托对象 + * @param obj:被委托对象 + * @param o:传给被委托者的数据 + */ + void event(Subject s,Observer obj,Object o); + /** + * 委托的很多事件 + * @param s:委托对象 + * @param obj:被委托对象 + */ + void eventAll(Subject s,Object obj); + /** + * 获取唯一标识name + * @return name:委托对象的唯一标识(名字) + */ + String getName(); + } + + + /** + * 被委托者接口 + */ + public interface Observer { + /** + * 被委托者所要执行的事(方法即处理程序) + * @param s:委托者对象 + * @param data:委托需要做的事情数据 + */ + void doEvent(Subject s,Object data); + } + + import java.util.*; + /** + * 委托对象的类 + */ + public class Delegation implements Subject{ + /** + * 本类对象的唯一标识name + * @param name + */ + @SuppressWarnings("unused") + private String name; + /** + * 存储被委托者对象的集合,位于java.util包中 + */ + List l = new ArrayList<>(); + /** + * 构造方法 + */ + Delegation(String name){ + this.name = name; + } + /** + * 添加被委托对象的方法 + * @param obj:被委托对象 + */ + @Override + public void addObserver(Observer obj) { + // TODO Auto-generated method stub + if (l==null) { + throw new NullPointerException(); + } + else { + if (!l.contains(obj)) { + l.add(obj); + } + } + } + /** + * 委托的事件方法 + */ + @Override + public void event(Subject s,Observer obj,Object o) { + // TODO Auto-generated method stub + obj.doEvent(s,o); + } + /** + * 移除所有被委托对象 + */ + @Override + public void removeAll() { + // TODO Auto-generated method stub + l.clear(); + } + /** + * 全部被委托者要做的事件方法 + */ + @Override + public void eventAll(Subject s,Object obj) { + // TODO Auto-generated method stub + for(Observer o:l) { + o.doEvent(s,obj); + } + } + /** + * 获取唯一标识name + * @return name + */ + @Override + public String getName() { + return name; + } + } + + /** + * 被委托的对象的类 + */ + class A implements Observer{ + /** + * 被委托的对象的唯一标识 + */ + private String name; + A(String name){ + this.name=name; + } + /** + * 被委托对象要做的事情 + * @param data:事情数据 + */ + @Override + public void doEvent(Subject s,Object data) { + // TODO Auto-generated method stub + System.out.println(s.getName()+"你好,"+"我是"+name+",你让我"+data+"的事已经做完了!"); + } + + } + + + + /** + * 测试类,整个程序的入口 + * @author 张三 + * + */ + public class DelegationDemo { + + public static void main(String[] args) { + // TODO Auto-generated method stub + Delegation d = new Delegation("张三"); + A a = new A("李四"); + d.addObserver(a); + d.event(d, a, "买早餐"); + A b = new A("王五"); + d.addObserver(b); + d.eventAll(d, "要美女的联系方式"); + } + } + + +### Java 的委托机制 + + +java委托机制与观察者模式:委托机制的实现不再需要提取观察者抽象类,观察者和通知者互不依赖。java利用反射即可实现,代码实例如下: + + + public class Event { + private Object object; + + private String methodName; + + private Object[] params; + + private Class[] paramTypes; + + public Event(Object object,String method,Object...args) + { + this.object = object; + this.methodName = method; + this.params = args; + contractParamTypes(this.params); + } + + private void contractParamTypes(Object[] params) + { + this.paramTypes = new Class[params.length]; + for (int i=0;i objects; + + public EventHandler() + { + objects = new ArrayList(); + } + + public void addEvent(Object object, String methodName, Object...args) + { + objects.add(new Event(object, methodName, args)); + } + + public void notifyX() throws Exception + { + for (Event event : objects) + { + event.invoke(); + } + } + } + + + + public abstract class Notifier { + private EventHandler eventHandler = new EventHandler(); + + public EventHandler getEventHandler() + { + return eventHandler; + } + + public void setEventHandler(EventHandler eventHandler) + { + this.eventHandler = eventHandler; + } + + public abstract void addListener(Object object,String methodName, Object...args); + + public abstract void notifyX(); + + } + + + public class ConcreteNotifier extends Notifier{ + + @Override + public void addListener(Object object, String methodName, Object... args) { + this.getEventHandler().addEvent(object, methodName, args); + } + + @Override + public void notifyX() { + try { + this.getEventHandler().notifyX(); + } catch (Exception e) { + // TODO: handle exception + e.printStackTrace(); + } + } + } + + // 具体观察者不在依赖任何接口 抽象 + public class WatchingTVListener { + + public WatchingTVListener() + { + System.out.println("watching TV"); + } + + public void stopWatchingTV(Date date) + { + System.out.println("stop watching" + date); + } + } + // 具体观察者不在依赖任何接口 抽象 + public class PlayingGameListener { + public PlayingGameListener() + { + System.out.println("playing"); + } + + public void stopPlayingGame(Date date) + { + System.out.println("stop playing" + date); + } + } + + public class Test { + + public static void main (String[] args) + { + Notifier goodNotifier = new ConcreteNotifier(); + + PlayingGameListener playingGameListener = new PlayingGameListener(); + + WatchingTVListener watchingTVListener = new WatchingTVListener(); + + goodNotifier.addListener(playingGameListener, "stopPlayingGame", new Date()); + + goodNotifier.addListener(watchingTVListener, "stopWatchingTV", new Date()); + + goodNotifier.notifyX(); + } + + } \ No newline at end of file diff --git a/sources/designpattern/facade.md b/sources/designpattern/facade.md new file mode 100644 index 0000000..1889003 --- /dev/null +++ b/sources/designpattern/facade.md @@ -0,0 +1,27 @@ +### 外观模式 + + +- 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得子系统更易于使用。 + + +- 介绍 +外观模式属于结构型模式。 +外观模式也叫门面模式。 +通常我们对API进行封装,都会用到外观模式,只是我们可能不知道而已。外观模式通过一个外观类使得整个系统的结构只有一个统一的高层接口,这样能降低用户的使用成本。 + + + +一搬 我们封装SDK的时候,通常对外提供统一的Mananger 去调用,提供统一入口, 比如EventBus,Glide等等 + + + +降低了客户端与子系统类的耦合度,实现了子系统与客户之间的松耦合关系。 +外观类对子系统的接口封装,使得系统更易于使用。 +提高灵活性,不管子系统如何变化,只要不影响门面对象,就可以自由修改。 + +7. 缺点 + +增加新的子系统可能需要修改外观类的源代码,违背了“开闭原则”。一搬我们SDK可以进行jar替换更新,对于一个系统 + +所有子系统的功能都通过一个接口来提供,这个接口可能会变得很复杂。 + diff --git a/sources/designpattern/factory.md b/sources/designpattern/factory.md new file mode 100644 index 0000000..4eed402 --- /dev/null +++ b/sources/designpattern/factory.md @@ -0,0 +1,62 @@ +### 工厂模式 + + +//抽象产品类 + + public abstract class Product { + public abstract void show(); + } + + 创建具体产品类,继承Product类: + + //具体产品类A + + public class ProductA extends Product { + @Override + public void show() { + System.out.println("product A"); + } + } + //具体产品类B + + public class ProductB extends Product { + @Override + public void show() { + System.out.println("product B"); + } + } + + + //抽象工厂类 + + public abstract class Factory { + public abstract Product create(); + } + + //创建具体工厂类,继承抽象工厂类,实现创建具体的产品: + + //具体工厂类A + public class FactoryA extends Factory { + @Override + public Product create() { + return new ProductA();//创建ProductA + } + } + //具体工厂类B + public class FactoryB extends Factory { + @Override + public Product create() { + return new ProductB();//创建ProductB + } + } + +优点 + + 符合开放封闭原则。新增产品时,只需增加相应的具体产品类和相应的工厂子类即可。 + 符合单一职责原则。每个具体工厂类只负责创建对应的产品。 + +缺点 + + 一个具体工厂只能创建一种具体产品。 + 增加新产品时,还需增加相应的工厂类,系统类的个数将成对增加,增加了系统的复杂度和性能开销。 + 引入的抽象类也会导致类结构的复杂化。 diff --git a/sources/designpattern/flyweightpattern.md b/sources/designpattern/flyweightpattern.md new file mode 100644 index 0000000..989d09c --- /dev/null +++ b/sources/designpattern/flyweightpattern.md @@ -0,0 +1,91 @@ +#### 享元模式 + +1. 介绍 + + 享元模式属于结构型模式。 + + 享元模式是池技术的重要实现方式,它可以减少重复对象的创建,使用缓存来共享对象,从而降低内存的使用。 + + 细粒度的对象其状态可以分为两种:内部状态和外部状态。 + + 内部状态:对象可共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变。 + + 外部状态:对象依赖的一个标记是随环境改变而改变的,并且不可共享。 + +2. Java 中享元模式,我们接触到最多的还是Java中的String。如果字符串常量池中有此字符则直接返回,否则先在字符串常量池中创建字符串。 + + + String例子 + String s0 = "abc"; + String s1 = "abc"; + + System.out.println("s0 == s1 " + s0 == s1); + +3.Message类本身就组织了一个栈结构的缓冲池。并使用obtain()方法和recycler()方法来取出和放入 + + + /*package*/ Message next; + + private static final Object sPoolSync = new Object(); + private static Message sPool; + private static int sPoolSize = 0; + + private static final int MAX_POOL_SIZE = 50; + + private static boolean gCheckRecycle = true; + 在解释这段代码前,需要先明确两点:sPool声明为private static Message sPool; + + next声明为/*package*/ Message next;。即前者为该类所有示例共享,后者则每个实例都有。 + + public static Message obtain() { + synchronized (sPoolSync) { + /**请注意!我们可以看到Message中有一个next字段指向下一个Message,这里就明白了,Message消息池中 + 没有使用Map这样的容器,而是使用的链表! + */ + if (sPool != null) { + Message m = sPool; + sPool = m.next; + m.next = null; + m.flags = 0; // clear in-use flag + sPoolSize--; + return m; + } + } + return new Message(); + } + + + void recycleUnchecked() { + // Mark the message as in use while it remains in the recycled object pool. + // Clear out all other details. + flags = FLAG_IN_USE; + what = 0; + arg1 = 0; + arg2 = 0; + obj = null; + replyTo = null; + sendingUid = -1; + when = 0; + target = null; + callback = null; + data = null; + + synchronized (sPoolSync) { + if (sPoolSize < MAX_POOL_SIZE) { + //将自身链入头部,所以next 指向之前的链表头 + next = sPool; + //链表的头指向自身 + sPool = this; + //sPoolSize 多了一个 + sPoolSize++; + } + } + } + + spool 和 next 在执行 recycleUnchecked 被赋值 也就是当前对象执行完 我们去回收他, + + Message3 next=Message2 spool=Message3 Message2 next = Message spool = Message2 Message next= null spool = Message + + +4. 开发时 多使用 Message.obtain().sendToMessage()形式发送消息可以提高性能,不要再使用Message() + diff --git a/sources/designpattern/mediator.md b/sources/designpattern/mediator.md new file mode 100644 index 0000000..0e75689 --- /dev/null +++ b/sources/designpattern/mediator.md @@ -0,0 +1,151 @@ +### 中介模式 + + +什么是中介者模式? + + +中介模式一般 中介和其他角色互相依赖,然后通过公共操作去调度,类似于MVP中P,MVC中C 等等 + + + +在现实生活中,有很多中介者模式的身影,例如QQ游戏平台,聊天室、QQ群、短信平台和房产中介。不论是QQ游戏还是QQ群,它们都是充当一个中间平台,QQ用户可以登录这个中间平台与其他QQ用户进行交流,如果没有这些中间平台,我们如果想与朋友进行聊天的话,可能就需要当面才可以了。电话、短信也同样是一个中间平台,有了这个中间平台,每个用户都不要直接依赖与其他用户,只需要依赖这个中间平台就可以了,一切操作都由中间平台去分发。 +中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。 + + +以现实生活中打牌的例子来实现下中介者模式。打牌总有输赢,对应的则是货币的变化,如果不用中介者模式的话,实现如下: + + + public abstract class AbstractCardPartner + { + public int Money { get; set; } + + public abstract void ChangeMoney(int money, AbstractCardPartner other); + } + + + public class PartnerA : AbstractCardPartner + { + public override void ChangeMoney(int money, AbstractCardPartner other) + { + Money += money; + other.Money -= money; + } + } + + public class PartnerB : AbstractCardPartner + { + public override void ChangeMoney(int money, AbstractCardPartner other) + { + Money += money; + other.Money -= money; + } + } + + + static void Main(string[] args) + { + AbstractCardPartner A = new PartnerA(); + A.Money = 20; + AbstractCardPartner B = new PartnerB(); + B.Money = 20; + + // A赢了B的钱减少 + A.ChangeMoney(5, B); + Console.WriteLine("A 现在的钱是:{0}", A.Money); // 应该是25 + Console.WriteLine("B 现在的钱是:{0}", B.Money); // 应该是15 + + // B赢了A的钱减少 + B.ChangeMoney(10, A); + Console.WriteLine("A 现在的钱是:{0}", A.Money); // 应该是15 + Console.WriteLine("B 现在的钱是:{0}", B.Money); // 应该是25 + + Console.ReadLine(); + } + + +这样的实现确实解决了上面场景中的问题,并且使用了抽象类使具体牌友A和牌友B都依赖于抽象类,从而降低了同事类之间的耦合度。但是如果其中牌友A发生变化时,此时就会影响到牌友B的状态,如果涉及的对象变多的话,这时候某一个牌友的变化将会影响到其他所有相关联的牌友状态。例如牌友A算错了钱,这时候牌友A和牌友B的钱数都不正确了,如果是多个人打牌的话,影响的对象就会更多。这时候就会思考——能不能把算钱的任务交给程序或者算数好的人去计算呢,这时候就有了我们QQ游戏中的欢乐斗地主等牌类游戏了。 +进一步完善的方案,即加入一个中介者对象来协调各个对象之间的关联,这也就是中介者模式的应用了,具体完善后的实现代码如下所示: + + + public abstract class AbstractCardPartner + { + public int Money { get; set; } + + public abstract void ChangeMoney(int money, AbstractMediator mediator); + } + + + public class PartnerA : AbstractCardPartner + { + public override void ChangeMoney(int money, AbstractMediator mediator) + { + mediator.AWin(money); + } + } + + + public class PartnerB : AbstractCardPartner + { + public override void ChangeMoney(int money, AbstractMediator mediator) + { + mediator.BWin(money); + } + } + + + + public abstract class AbstractMediator + { + protected AbstractCardPartner A; + protected AbstractCardPartner B; + + public AbstractMediator(AbstractCardPartner a, AbstractCardPartner b) + { + A = a; + B = b; + } + + public abstract void AWin(int money); + public abstract void BWin(int money); + } + + + static void Main(string[] args) + { + AbstractCardPartner A = new PartnerA(); + AbstractCardPartner B = new PartnerB(); + A.Money = 20; + B.Money = 20; + + AbstractMediator mediator = new MediatorPater(A, B); + + // A赢了 + A.ChangeMoney(5, mediator); + Console.WriteLine("A 现在的钱是:{0}", A.Money); // 应该是25 + Console.WriteLine("B 现在的钱是:{0}", B.Money); // 应该是15 + + // B赢了 + B.ChangeMoney(10, mediator); + Console.WriteLine("A 现在的钱是:{0}", A.Money); // 应该是15 + Console.WriteLine("B 现在的钱是:{0}", B.Money); // 应该是25 + + Console.ReadLine(); + } + + +在上面的实现代码中,抽象中介者类保存了两个抽象牌友类,如果新添加一个牌友类似时,此时就不得不去更改这个抽象中介者类。可以结合观察者模式来解决这个问题,即抽象中介者对象保存抽象牌友的类别,然后添加Register和UnRegister方法来对该列表进行管理,然后在具体中介者类中修改AWin和BWin方法,遍历列表,改变自己和其他牌友的钱数。这样的设计还是存在一个问题——即增加一个新牌友时,此时虽然解决了抽象中介者类不需要修改的问题,但此时还是不得不去修改具体中介者类,即添加CWin方法,我们可以采用状态模式来解决这个问题,关于状态模式的介绍将会在下一篇进行介绍。 + +中介者模式的优缺点 +优点: + +简化了对象之间的关系,将系统的各个对象之间的相互关系进行封装,将各个同事类解耦,使得系统变为松耦合。 +提供系统的灵活性,使得各个同事对象独立而易于复用。 +缺点: + +中介者模式中,中介者角色承担了较多的责任,所以一旦这个中介者对象出现了问题,整个系统将会受到重大的影响。 +新增加一个同事类时,不得不去修改抽象中介者类和具体中介者类,此时可以使用观察者模式和状态模式来解决这个问题。 +中介者模式的适用场景 +以下情况下可以考虑使用中介者模式: + +一组定义良好的对象,现在要进行复杂的相互通信。 +想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。 \ No newline at end of file diff --git a/sources/designpattern/observer.md b/sources/designpattern/observer.md new file mode 100644 index 0000000..2d32499 --- /dev/null +++ b/sources/designpattern/observer.md @@ -0,0 +1,99 @@ +### 观察者模式 + + + +- 观察者抽象 + + + public interface Observer {//抽象观察者 + public void update(String message);//更新方法 + } + + + +- 创建具体观察者实现抽象观察者中的方法,这里创建两个类,一个男孩类和一个女孩类,定义他们收到通知后的反应: + + + public class Boy implements Observer { + + private String name;//名字 + public Boy(String name) { + this.name = name; + } + @Override + public void update(String message) {//男孩的具体反应 + System.out.println(name + ",收到了信息:" + message+"屁颠颠的去取快递."); + } + } + + public class Girl implements Observer { + + private String name;//名字 + public Girl(String name) { + this.name = name; + } + @Override + public void update(String message) {//女孩的具体反应 + System.out.println(name + ",收到了信息:" + message+"让男朋友去取快递~"); + } + } + +- 创建抽象主题即抽象被观察者,定义添加,删除,通知等方法: + + + public interface Observable {//抽象被观察者 + void add(Observer observer);//添加观察者 + + void remove(Observer observer);//删除观察者 + + void notify(String message);//通知观察者 + } +- 创建具体主题即具体被观察者,也就是快递员,派送快递时根据快递信息来通知收件人让其来取件: + + + public class Postman implements Observable{//快递员 + + private List personList = new ArrayList();//保存收件人(观察者)的信息 + @Override + public void add(Observer observer) {//添加收件人 + personList.add(observer); + } + + @Override + public void remove(Observer observer) {//移除收件人 + personList.remove(observer); + + } + + @Override + public void notify(String message) {//逐一通知收件人(观察者) + for (Observer observer : personList) { + observer.update(message); + } + } + } + +- 客户端测试 + + + public void test(){ + Observable postman=new Postman(); + + Observer boy1=new Boy("路飞"); + Observer boy2=new Boy("乔巴"); + Observer girl1=new Girl("娜美"); + + postman.add(boy1); + postman.add(boy2); + postman.add(girl1); + + postman.notify("快递到了,请下楼领取."); + } + + +- 应用场景 + + + 当一个对象的改变需要通知其它对象改变时,而且它不知道具体有多少个对象有待改变时。 + 当一个对象必须通知其它对象,而它又不能假定其它对象是谁 + 跨系统的消息交换场景,如消息队列、事件总线的处理机制。 \ No newline at end of file diff --git a/sources/designpattern/prototype.md b/sources/designpattern/prototype.md new file mode 100644 index 0000000..a69f74e --- /dev/null +++ b/sources/designpattern/prototype.md @@ -0,0 +1,9 @@ +### 原型模式 + + +### 就是再已经存在对象,创建一个新的一模一样的对象 + + +java 原型需要实现Cloneable方法 + +参考 - [Java 克隆](https://github.com/UCodeUStory/DataStructure/blob/master/sources/javaCopy.md) \ No newline at end of file diff --git a/sources/designpattern/proxy.md b/sources/designpattern/proxy.md new file mode 100644 index 0000000..d1185e0 --- /dev/null +++ b/sources/designpattern/proxy.md @@ -0,0 +1,153 @@ +### 代理模式 + + +1. 介绍 + + + 代理模式属于结构型模式。 + 代理模式也叫委托模式。 + 生活中,比如代购、打官司等等,实际上都是一种代理模式。 + + + + public interface People { + + void buy();//购买 + } + +2. 创建真实主题类 + + + 国内的人想购买某些产品,定义具体的购买过程: + + public class Domestic implements People { + + @Override + public void buy() {//具体实现 + System.out.println("国内要买一个包"); + } + } + +3. 创建代理类 + 海外的代购党需要知道是谁(持有真实主题类的引用)想购买啥产品: + + + public class Oversea implements People { + People mPeople;//持有People类的引用 + + public Oversea(People people) { + mPeople = people; + } + + @Override + public void buy() { + System.out.println("我是海外代购:"); + mPeople.buy();//调用了被代理者的buy()方法, + } + } + + 客户端测试: + public void test() { + People domestic = new Domestic(); //创建国内购买人 + People oversea = new Oversea(domestic); //创建海外代购类并将domestic作为构造函数传递 + oversea.buy(); //调用海外代购的buy() + } + + 输出结果: + 我是海外代购: + 国内要买一个包 + + 静态代理与动态代理 + + +   从代码的角度来分,代理可以分为两种:一种是静态代理,另一种是动态代理。 +   静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。上面的例子实现就是静态代理。 +   动态代理类的源码是在程序运行期间根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 +   下面我们实现动态代理,Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke()方法: + + 创建动态代理类 + + + public class DynamicProxy implements InvocationHandler {//实现InvocationHandler接口 + private Object obj;//被代理的对象 + + public DynamicProxy(Object obj) { + this.obj = obj; + } + + //重写invoke()方法 + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("海外动态代理调用方法: "+method.getName()); + Object result = method.invoke(obj, args);//调用被代理的对象的方法 + return result; + } + } + + 修改客户端的测试方法: + + + public void test() { + People domestic = new Domestic(); //创建国内购买人 + DynamicProxy proxy = new DynamicProxy(domestic); //创建动态代理 + ClassLoader classLoader = domestic.getClass().getClassLoader(); //获取ClassLoader + People oversea = (People) Proxy.newProxyInstance(classLoader, new Class[]{People.class}, proxy); //通过 Proxy 创建海外代购实例 ,实际上通过反射来实现的。 + oversea.buy();//调用海外代购的buy() + } + + 输出结果: + 海外动态代理调用方法: buy + 国内要买一个包 + +- 静态代理与动态代理比较 + 静态代理的缺点: + + + 静态代理如果接口新增一个方法,除了所有实现类(真实主题类)需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。 + 代理对象只服务于一种类型的对象,如果要服务多类型的对象。必须要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。 + + +- 动态代理的优点: + + + 可以通过一个代理类完成全部的代理功能,接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。当接口方法数量较多时,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。 + 动态代理的应用使我们的类职责更加单一,复用性更强。 + + +- 动态代理的缺点: + + + 不能对类进行代理,只能对接口进行代理,如果我们的类没有实现任何接口,那么就不能使用这种方式进行动态代理(因为$Proxy()这个类集成了Proxy,Java的集成不允许出现多个父类)。 + + +- 应用场景 + + + + 当一个对象不能或者不想直接访问另一个对象时,可以通过一个代理对象来间接访问。为保证客户端使用的透明性,委托对象和代理对象要实现同样的接口。 + 被访问的对象不想暴露全部内容时,可以通过代理去掉不想被访问的内容。 + + 根据适用范围,代理模式可以分为以下几种: + + + 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的事项隐藏。 + + 虚拟代理:如果要创建一个资源消耗较大的对象,可以先用一个代理对象表示,在真正需要的时候才真正创建。 + + 保护代理:用代理对象控制对一个对象的访问,给不同的用户提供不同的访问权限。 + + 智能引用:在引用原始对象的时候附加额外操作,并对指向原始对象的引用增加引用计数。 + + + + 优点 + + 代理作为调用者和真实主题的中间层,降低了模块间和系统的耦合性。 + 可以以一个小对象代理一个大对象,达到优化系统提高运行速度的目的。 + 代理对象能够控制调用者的访问权限,起到了保护真实主题的作用。 + + 缺点 + + 由于在调用者和真实主题之间增加了代理对象,因此可能会造成请求的处理速度变慢。 + 实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。 + diff --git a/sources/designpattern/proxyidiffdecorator.md b/sources/designpattern/proxyidiffdecorator.md new file mode 100644 index 0000000..9cf563a --- /dev/null +++ b/sources/designpattern/proxyidiffdecorator.md @@ -0,0 +1,10 @@ +### 装饰器模式和代理模式区别 + +代理和装饰两个模式从代码结构上看是完全一样的,一点也没有区别 + +代理模式:侧重于控制对象的调用时机,判断,流程 + +装饰类:侧重于扩展功能,如果添加的功能多,可以认为是装饰模式 + + +设计模式本身是为了提升代码的可扩展性,灵活运用即可,不必生搬硬套,非要分出个所以然来 \ No newline at end of file diff --git a/sources/designpattern/simpleFactory.md b/sources/designpattern/simpleFactory.md new file mode 100644 index 0000000..7a431d2 --- /dev/null +++ b/sources/designpattern/simpleFactory.md @@ -0,0 +1,72 @@ +### 简单工厂 + + +1. 抽象产品类 + + + public abstract class Product { + public abstract void show(); + } + +2. 创建具体产品类,继承Product类: + + + //具体产品类A + public class ProductA extends Product { + @Override + public void show() { + System.out.println("product A"); + } + } + //具体产品类B + public class ProductB extends Product { + @Override + public void show() { + System.out.println("product B"); + } + } + +3. 创建工厂类,创建具体的产品: + + + public class Factory { + + public static Product create(String productName) { + Product product = null; + //通过switch语句控制生产哪种商品 + switch (productName) { + case "A": + product = new ProductA(); + break; + case "B": + product = new ProductB(); + break; + } + return product; + } + } + + +工厂方法模式有抽象工厂类,简单工厂模式没有抽象工厂类,且其工厂类的工厂方法是静态的。 + +工厂方法模式新增产品时只需新建一个工厂类即可,符合开放封闭原则;而简单工厂模式需要直接修改工厂类,违反了开放封闭原则。 + + +优化:由于简单工厂模式新增产品时需要直接修改工厂类,违反了开放封闭原则。因此可以使用反射来创建实例对象,确保能够遵循开放封闭原则。 + + +反射实现工厂类 + + + public class Factory { + + public static T create(Class clz) { + Product product = null; + try { + product = (Product) Class.forName(clz.getName()).newInstance();//反射出实例 + } catch (Exception e) { + e.printStackTrace(); + } + return (T) product; + } + diff --git a/sources/designpattern/state.md b/sources/designpattern/state.md new file mode 100644 index 0000000..259a555 --- /dev/null +++ b/sources/designpattern/state.md @@ -0,0 +1,441 @@ +### 状态模式 + + +**对于可以切换状态的状态模式不满足“开闭原则”的要求。** + + +1.概述 + +在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理。最直接的解决方案是将这些所有可能发生的情况全都考虑到。然后使用if... ellse语句来做状态判断来进行不同情况的处理。但是对复杂状态的判断就显得“力不从心了”。随着增加新的状态或者修改一个状体(if else(或switch case)语句的增多或者修改)可能会引起很大的修改,而程序的可读性,扩展性也会变得很弱。维护也会很麻烦。那么我就考虑只修改自身状态的模式。 + +例子1:按钮来控制一个电梯的状态,一个电梯开们,关门,停,运行。每一种状态改变,都有可能要根据其他状态来更新处理。例如,开门状体,你不能在运行的时候开门,而是在电梯定下后才能开门。 + +例子2:我们给一部手机打电话,就可能出现这几种情况:用户开机,用户关机,用户欠费停机,用户消户等。 所以当我们拨打这个号码的时候:系统就要判断,该用户是否在开机且不忙状态,又或者是关机,欠费等状态。但不管是那种状态我们都应给出对应的处理操作。 + +2.问题 + +对象如何在每一种状态下表现出不同的行为? + +3.解决方案 + +状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 + +在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。 +4.适用性 + +在下面的两种情况下均可使用State模式: +if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。 +5.结构 + + +6.模式的组成 + +环境类(Context): 定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。 +抽象状态类(State): 定义一个接口以封装与Context的一个特定状态相关的行为。 +具体状态类(ConcreteState): 每一子类实现一个与Context的一个状态相关的行为。 + +7.效果 + +State模式有下面一些效果: +状态模式的优点: +1 ) 它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来: State模式将所有与一个特定的状态相关的行为都放入一个对象中。因为所有与状态相关的代码都存在于某一个State子类中, 所以通过定义新的子类可以很容易的增加新的状态和转换。另一个方法是使用数据值定义内部状态并且让 Context操作来显式地检查这些数据。但这样将会使整个Context的实现中遍布看起来很相似的条件if else语句或switch case语句。增加一个新的状态可能需要改变若干个操作, 这就使得维护变得复杂了。State模式避免了这个问题, 但可能会引入另一个问题, 因为该模式将不同状态的行为分布在多个State子类中。这就增加了子类的数目,相对于单个类的实现来说不够紧凑。但是如果有许多状态时这样的分布实际上更好一些, 否则需要使用巨大的条件语句。正如很长的过程一样,巨大的条件语句是不受欢迎的。它们形成一大整块并且使得代码不够清晰,这又使得它们难以修改和扩展。 State模式提供了一个更好的方法来组织与特定状态相关的代码。决定状态转移的逻辑不在单块的 i f或s w i t c h语句中, 而是分布在State子类之间。将每一个状态转换和动作封装到一个类中,就把着眼点从执行状态提高到整个对象的状态。这将使代码结构化并使其意图更加清晰。 + +2) 它使得状态转换显式化: 当一个对象仅以内部数据值来定义当前状态时 , 其状态仅表现为对一些变量的赋值,这不够明确。为不同的状态引入独立的对象使得转换变得更加明确。而且, State对象可保证Context不会发生内部状态不一致的情况,因为从 Context的角度看,状态转换是原子的—只需重新绑定一个变量(即Context的State对象变量),而无需为多个变量赋值 + +3) State对象可被共享 如果State对象没有实例变量—即它们表示的状态完全以它们的类型来编码—那么各Context对象可以共享一个State对象。当状态以这种方式被共享时, 它们必然是没有内部状态, 只有行为的轻量级对象。 + +状态模式的缺点: +1) 状态模式的使用必然会增加系统类和对象的个数。 +2) 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 +8.实现 + +我们用电梯的例子来说明: + +简单地实现代码: + + + + abstract class ILift { + //电梯的四个状态 + const OPENING_STATE = 1; //门敞状态 + const CLOSING_STATE = 2; //门闭状态 + const RUNNING_STATE = 3; //运行状态 + const STOPPING_STATE = 4; //停止状态; + + + //设置电梯的状态 + public abstract function setState($state); + + //首先电梯门开启动作 + public abstract function open(); + + //电梯门有开启,那当然也就有关闭了 + public abstract function close(); + + //电梯要能上能下,跑起来 + public abstract function run(); + + //电梯还要能停下来,停不下来那就扯淡了 + public abstract function stop(); + } + + /** + * 电梯的实现类 + */ + class Lift extends ILift { + private $state; + + public function setState($state) { + $this->state = $state; + } + //电梯门关闭 + public function close() { + //电梯在什么状态下才能关闭 + switch($this->state){ + case ILift::OPENING_STATE: //如果是则可以关门,同时修改电梯状态 + $this->setState(ILift::CLOSING_STATE); + break; + case ILift::CLOSING_STATE: //如果电梯就是关门状态,则什么都不做 + //do nothing; + return ; + break; + case ILift::RUNNING_STATE: //如果是正在运行,门本来就是关闭的,也说明都不做 + //do nothing; + return ; + break; + case ILift::STOPPING_STATE: //如果是停止状态,本也是关闭的,什么也不做 + //do nothing; + return ; + break; + } + echo 'Lift colse
'; + } + + //电梯门开启 + public function open() { + //电梯在什么状态才能开启 + switch($this->state){ + case ILift::OPENING_STATE: //如果已经在门敞状态,则什么都不做 + //do nothing; + return ; + break; + case ILift::CLOSING_STATE: //如是电梯时关闭状态,则可以开启 + $this->setState(ILift::OPENING_STATE); + break; + case ILift::RUNNING_STATE: //正在运行状态,则不能开门,什么都不做 + //do nothing; + return ; + break; + case ILift::STOPPING_STATE: //停止状态,淡然要开门了 + $this->setState(ILift::OPENING_STATE); + break; + } + echo 'Lift open
'; + } + ///电梯开始跑起来 + public function run() { + switch($this->state){ + case ILift::OPENING_STATE: //如果已经在门敞状态,则不你能运行,什么都不做 + //do nothing; + return ; + break; + case ILift::CLOSING_STATE: //如是电梯时关闭状态,则可以运行 + $this->setState(ILift::RUNNING_STATE); + break; + case ILift::RUNNING_STATE: //正在运行状态,则什么都不做 + //do nothing; + return ; + break; + case ILift::STOPPING_STATE: //停止状态,可以运行 + $this->setState(ILift::RUNNING_STATE); + } + echo 'Lift run
'; + } + + //电梯停止 + public function stop() { + switch($this->state){ + case ILift::OPENING_STATE: //如果已经在门敞状态,那肯定要先停下来的,什么都不做 + //do nothing; + return ; + break; + case ILift::CLOSING_STATE: //如是电梯时关闭状态,则当然可以停止了 + $this->setState(ILift::CLOSING_STATE); + break; + case ILift::RUNNING_STATE: //正在运行状态,有运行当然那也就有停止了 + $this->setState(ILift::CLOSING_STATE); + break; + case ILift::STOPPING_STATE: //停止状态,什么都不做 + //do nothing; + return ; + break; + } + echo 'Lift stop
'; + } + + } + $lift = new Lift(); + + //电梯的初始条件应该是停止状态 + $lift->setState(ILift::STOPPING_STATE); + //首先是电梯门开启,人进去 + $lift->open(); + + //然后电梯门关闭 + $lift->close(); + + //再然后,电梯跑起来,向上或者向下 + $lift->run(); + //最后到达目的地,电梯挺下来 + $lift->stop(); + 显然我们已经完成了我们的基本业务操作,但是,我们在程序中使用了大量的switch…case这样的判断(if…else也是一样),首先是程序的可阅读性很差,其次扩展非常不方便。一旦我们有新的状态加入的话,例如新加通电和断点状态。我们势必要在每个业务方法里边增加相应的case语句。也就是四个函数open,close,run,stop都需要修改相应case语句。 + + 状态模式:把不同状态的操作分散到不同的状态对象里去完成。看看状态类的uml类图: + + + + /** + * + * 定义一个电梯的接口 + */ + abstract class LiftState{ + + //定义一个环境角色,也就是封装状态的变换引起的功能变化 + protected $_context; + + public function setContext(Context $context){ + $this->_context = $context; + } + + //首先电梯门开启动作 + public abstract function open(); + + //电梯门有开启,那当然也就有关闭了 + public abstract function close(); + + //电梯要能上能下,跑起来 + public abstract function run(); + + //电梯还要能停下来,停不下来那就扯淡了 + public abstract function stop(); + + } + + + /** + * 环境类:定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。 + */ + class Context { + //定义出所有的电梯状态 + static $openningState = null; + static $closeingState = null; + static $runningState = null; + static $stoppingState = null; + + public function __construct() { + self::$openningState = new OpenningState(); + self::$closeingState = new ClosingState(); + self::$runningState = new RunningState(); + self::$stoppingState = new StoppingState(); + + } + + //定一个当前电梯状态 + private $_liftState; + + public function getLiftState() { + return $this->_liftState; + } + + public function setLiftState($liftState) { + $this->_liftState = $liftState; + //把当前的环境通知到各个实现类中 + $this->_liftState->setContext($this); + } + + + public function open(){ + $this->_liftState->open(); + } + + public function close(){ + $this->_liftState->close(); + } + + public function run(){ + $this->_liftState->run(); + } + + public function stop(){ + $this->_liftState->stop(); + } + } + + /** + * 在电梯门开启的状态下能做什么事情 + */ + class OpenningState extends LiftState { + + /** + * 开启当然可以关闭了,我就想测试一下电梯门开关功能 + * + */ + public function close() { + //状态修改 + $this->_context->setLiftState(Context::$closeingState); + //动作委托为CloseState来执行 + $this->_context->getLiftState()->close(); + } + + //打开电梯门 + public function open() { + echo 'lift open...', '
'; + } + //门开着电梯就想跑,这电梯,吓死你! + public function run() { + //do nothing; + } + + //开门还不停止? + public function stop() { + //do nothing; + } + + } + + /** + * 电梯门关闭以后,电梯可以做哪些事情 + */ + class ClosingState extends LiftState { + + //电梯门关闭,这是关闭状态要实现的动作 + public function close() { + echo 'lift close...', '
'; + + } + //电梯门关了再打开,逗你玩呢,那这个允许呀 + public function open() { + $this->_context->setLiftState(Context::$openningState); //置为门敞状态 + $this->_context->getLiftState()->open(); + } + + //电梯门关了就跑,这是再正常不过了 + public function run() { + $this->_context->setLiftState(Context::$runningState); //设置为运行状态; + $this->_context->getLiftState()->run(); + } + + //电梯门关着,我就不按楼层 + + public function stop() { + $this->_context->setLiftState(Context::$stoppingState); //设置为停止状态; + $this->_context->getLiftState()->stop(); + } + + } + + /** + * 电梯在运行状态下能做哪些动作 + */ + class RunningState extends LiftState { + + //电梯门关闭?这是肯定了 + public function close() { + //do nothing + } + + //运行的时候开电梯门?你疯了!电梯不会给你开的 + public function open() { + //do nothing + } + + //这是在运行状态下要实现的方法 + public function run() { + echo 'lift run...', '
'; + } + + //这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了 + public function stop() { + $this->_context->setLiftState(Context::$stoppingState); //环境设置为停止状态; + $this->_context->getLiftState()->stop(); + } + + } + + + + /** + * 在停止状态下能做什么事情 + */ + class StoppingState extends LiftState { + + //停止状态关门?电梯门本来就是关着的! + public function close() { + //do nothing; + } + + //停止状态,开门,那是要的! + public function open() { + $this->_context->setLiftState(Context::$openningState); + $this->_context->getLiftState()->open(); + } + //停止状态再跑起来,正常的很 + public function run() { + $this->_context->setLiftState(Context::$runningState); + $this->_context->getLiftState()->run(); + } + //停止状态是怎么发生的呢?当然是停止方法执行了 + public function stop() { + echo 'lift stop...', '
'; + } + + } + + /** + * 模拟电梯的动作 + */ + class Client { + + public static function main() { + $context = new Context(); + $context->setLiftState(new ClosingState()); + + $context->open(); + $context->close(); + $context->run(); + $context->stop(); + } + } + Client::main(); + + + +9.与其他相关模式 + +1)职责链模式, +职责链模式和状态模式都可以解决If分支语句过多, +从定义来看,状态模式是一个对象的内在状态发生改变(一个对象,相对比较稳定,处理完一个对象下一个对象的处理一般都已确定), +而职责链模式是多个对象之间的改变(多个对象之间的话,就会出现某个对象不存在的现在,就像我们举例的公司请假流程,经理可能不在公司情况),这也说明他们两个模式处理的情况不同。 +这两个设计模式最大的区别就是状态模式是让各个状态对象自己知道其下一个处理的对象是谁。 +而职责链模式中的各个对象并不指定其下一个处理的对象到底是谁,只有在客户端才设定。 +用我们通俗的编程语言来说,就是 +状态模式: + 相当于If else if else; + + 设计路线:各个State类的内部实现(相当于If,else If内的条件) + + 执行时通过State调用Context方法来执行。 + +职责链模式: + + 相当于Swich case + + 设计路线:客户设定,每个子类(case)的参数是下一个子类(case)。 + + 使用时,向链的第一个子类的执行方法传递参数就可以。 + +就像对设计模式的总结,有的人采用的是状态模式,从头到尾,提前一定定义好下一个处理的对象是谁,而我采用的是职责链模式,随时都有可能调整链的顺序。 + +10.总结与分析 + + 状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;其缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。 \ No newline at end of file diff --git a/sources/designpattern/stateAndStragetyDiff.md b/sources/designpattern/stateAndStragetyDiff.md new file mode 100644 index 0000000..0bedc6b --- /dev/null +++ b/sources/designpattern/stateAndStragetyDiff.md @@ -0,0 +1,15 @@ +状态模式是策略模式的孪生兄弟 + + + + 状态模式和策略模式的实现方法非常类似,都是利用多态把一些操作分配到一组相关的简单的类中,因此很多人认为这两种模式实际上是相同的。 +然而在现实世界中,策略(如促销一种商品的策略)和状态(如同一个按钮来控制一个电梯的状态,又如手机界面中一个按钮来控制手机)是两种完全不同的思想。 + + +当我们对状态和策略进行建模时,这种差异会导致完全不同的问题。例如,对状态进行建模时,状态迁移是一个核心内容;然而,在选择策略时,迁移与此毫无关系。另外,策略模式允许一个客户选择或提供一种策略,而这种思想在状态模式中完全没有。 + 一个策略是一个计划或方案,通过执行这个计划或方案,我们可以在给定的输入条件下达到一个特定的目标。策略是一组方案,他们可以相互替换;选择一个策略,获得策略的输出。策略模式用于随不同外部环境采取不同行为的场合。我们可以参考微软企业库底层Object Builder的创建对象的strategy实现方式。而状态模式不同,对一个状态特别重要的对象,通过状态机来建模一个对象的状态;状态模式处理的核心问题是状态的迁移,因为在对象存在很多状态情况下,对各个business flow,各个状态之间跳转和迁移过程都是及其复杂的。 + 例如一个工作流,审批一个文件,存在新建、提交、已修改、HR部门审批中、老板审批中、HR审批失败、老板审批失败等状态,涉及多个角色交互,涉及很多事件,这种情况下用状态模式(状态机)来建模更加合适;把各个状态和相应的实现步骤封装成一组简单的继承自一个接口或抽象类的类,通过另外的一个Context来操作他们之间的自动状态变换,通过event来自动实现各个状态之间的跳转。在整个生命周期中存在一个状态的迁移曲线,这个迁移曲线对客户是透明的。我们可以参考微软最新的WWF 状态机工作流实现思想。 + 在状态模式中,状态的变迁是由对象的内部条件决定,外界只需关心其接口,不必关心其状态对象的创建和转化; +而策略模式里,采取何种策略由外部条件(C)决定。 + 他们应用场景(目的)却不一样,State模式重在强调对象内部状态的变化改变对象的行为,Strategy模式重在外部对策略的选择,策略的选择由外部条件决定, +也就是说算法的动态的切换。但由于它们的结构是如此的相似,我们可以认为“状态模式是完全封装且自修改的策略模式”。即状态模式是封装对象内部的状态的,而策略模式是封装算法族的 diff --git a/sources/designpattern/stragetyAndcommanddiff.md b/sources/designpattern/stragetyAndcommanddiff.md new file mode 100644 index 0000000..d6ba6c9 --- /dev/null +++ b/sources/designpattern/stragetyAndcommanddiff.md @@ -0,0 +1,25 @@ +### 策略模式与命令模式区别 + + +1. 策略模式 定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可独立于它们的客户变化。 + +2. 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 + + + +我个人觉得,策略模式和命令模式的其中一个最大区别, +是在于:策略模式对付的问题域通常是一个,就是说,多个策略只是处理同一个问题,比如排序问题,使用不同的排序算法 + +而命令模式对付的是多个问题域,就是很多不同的命令来做不同的事情。 比如关机 开启 命令,开机和关机 是两种不同的事情 + +再比如,关机时,有程序没退出(可以采用一个一个退出的算法退出),也可以(采用直接杀死所有程序的方式) + + +比如 让机器人去跑步 和 吃饭 是命令模式,因为干的不一样的事,跑步 的路径选着 是策略模式 + + + +3. 如果是播放不同类型音乐,应该是命令模式,接收到不同的命令后去播放不同类型的音乐,同时还需要一个接收者知道播放的进度 + + +更多的情况命令模式比策略模式多了一个接收者 diff --git a/sources/designpattern/templateMethod.md b/sources/designpattern/templateMethod.md new file mode 100644 index 0000000..7123188 --- /dev/null +++ b/sources/designpattern/templateMethod.md @@ -0,0 +1,46 @@ +### 模板方法 + +1. 父类通过实现一套自己的算法流程,方法内部实现一些小的算法 + + + AbstractClass(抽象类):,定义了一整套算法框架。 + ConcreteClass(具体实现类):具体实现类,根据需要去实现抽象类中的方法 + +2. Android 中 + + + 继续以送快递为例,快递员送快递基本就是一套固定的流程:收到快递,准备派送->联系收货人->确定结果。 + + + public abstract class Postman {//抽象快递员类 + + //派送流程 + public final void post() {//这里申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序 + prepare();//准备派送 + call();//联系收货人 + if (isSign())//是否签收 + sign();//签收 + else refuse();//拒签 + } + + protected void prepare() {//准备操作,固定流程,父类实现 + System.out.println("快递已达到,准备派送"); + } + + protected abstract void call();//联系收货人,联系人不一样,所以为抽象方法,子类实现 + + protected boolean isSign() {//是否签收,这个是钩子方法,用来控制流程的走向 + return true; + } + + protected void sign() {//签收,这个是固定流程,父类实现 + System.out.println("客户已签收,上报系统"); + } + + protected void refuse() {//拒签,空实现,这个也是钩子方法,子类可以跟进实际来决定是否去实现这个方法 + } + } + + +3. 模板模式在Android 中广泛应用,不如Activity 生命周期、View 的绘制在 super 底层draw中都封装好了方法,AsyncTask + diff --git a/sources/eventbus.md b/sources/eventbus.md new file mode 100644 index 0000000..b6c894a --- /dev/null +++ b/sources/eventbus.md @@ -0,0 +1,367 @@ +### EventBus源码分析 +使用 EventBus 时注意 + + 1. 使用 EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();才是开启注解方式 + eventBus.register(this); + + 2. 而使用 EventBus.getDefault().register() 是使用反射的方式 + +#### 1.第一步:编译时期注解解析器 + +** 编译时 主要生成MyIndexEvent类 ** + + + /** This class is generated by EventBus, do not edit. */ + + public class MyEventBusIndex implements SubscriberInfoIndex { + private static final Map, SubscriberInfo> SUBSCRIBER_INDEX; + + static { + SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>(); + + putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.TestEventBusActivity.class, true, + new SubscriberMethodInfo[] { + new SubscriberMethodInfo("onPostTest", org.greenrobot.eventbusperf.testsubject.MEvent.class, + ThreadMode.MAIN), + })); + + putIndex(new SimpleSubscriberInfo(org.greenrobot.eventbusperf.testsubject.PerfTestEventBus.SubscriberClassEventBusAsync.class, + true, new SubscriberMethodInfo[] { + new SubscriberMethodInfo("onEventAsync", TestEvent.class, ThreadMode.ASYNC), + })); + + + + private static void putIndex(SubscriberInfo info) { + SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); + } + + @Override + public SubscriberInfo getSubscriberInfo(Class subscriberClass) { + SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); + if (info != null) { + return info; + } else { + return null; + } + } + } + + + +#### 2.EventBus 构造方法 + +Step1:EventBus .getDefault() 创建了一个单例,采用双校验方式 +Step2: 调用EventBus构造方法创建4个集合,集合的命令通过 dataByKey的形式 + + //以事件类型作为Key,Subscription的List集合作为Value的Map集合,后面post一个事件也就用到这个集合 + + 1. Map,CopyOnWriteArrayList> subscriptionsByEventType = new HashMap<>(); + //订阅者作为Key,订阅事件作为Value的Map集合 + + 2 Map>> typesBySubscriber = new HashMap<>(); + + 3.Map, Object> stickyEvents== new ConcurrentHashMap<>(); + 4.Map, List>> eventTypesCache = new HashMap<>(); + + 4.创建EventBusBuilder : + 5.创建一个BackgroundPoster: + 创建一个 HandlerPoster extends Handler implements Poster + + +EventBusBuilder构建构造方法中 创建三个集合 + + 1.创建一个线程池 ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE; + 2.List> skipMethodVerificationForClasses; + 3.List subscriberInfoIndexes; + MainThreadSupport mainThreadSupport; + + + + +#### 3. register方法干了什么? + +对于register中的参数,就是我们的订阅者,也就是我们经常传入的this对象。在这个方法中主要完成了两件事情。首先通过findSubscriberMethods方法来查找订阅者中所有的订阅方法。然后遍历订阅者的订阅方法来完成订阅者的订阅操作。 + + +首先在这里来看一下findSubscriberMethods这个方法是如何查找订阅者的订阅方法。在这先描述一下SubscriberMethod类。对于SubscriberMethod类中,主要就是用保存订阅方法的Method对象,线程模式,事件类型,优先级,是否粘性事件等属性。下面就来看一下findSubscriberMethods方法。 + + List findSubscriberMethods(Class subscriberClass) { + //从缓存中获取SubscriberMethod集合 + List subscriberMethods = METHOD_CACHE.get(subscriberClass); + if (subscriberMethods != null) { + return subscriberMethods; + } + //ignoreGeneratedIndex是否忽略注解器生成的MyEventBusIndex + if (ignoreGeneratedIndex) { + //通过反射获取subscriberMethods + subscriberMethods = findUsingReflection(subscriberClass); + } else { + //通过注解器生成的MyEventBusIndex信息获取subscriberMethods, + //如果没有配置MyEventBusIndex,依然通过通过反射获取subscriberMethods + subscriberMethods = findUsingInfo(subscriberClass); + } + if (subscriberMethods.isEmpty()) { + throw new EventBusException("Subscriber " + subscriberClass + + " and its super classes have no public methods with the @Subscribe annotation"); + } else { + METHOD_CACHE.put(subscriberClass, subscriberMethods); + return subscriberMethods; + } + } + + + + + private List findUsingInfo(Class subscriberClass) { + //创建和初始化FindState对象 + FindState findState = prepareFindState(); + findState.initForSubscriber(subscriberClass); + while (findState.clazz != null) { + //获取订阅者信息,没有配置MyEventBusIndex返回null + findState.subscriberInfo = getSubscriberInfo(findState); + if (findState.subscriberInfo != null) { + SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); + for (SubscriberMethod subscriberMethod : array) { + if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { + findState.subscriberMethods.add(subscriberMethod); + } + } + } else { + //通过反射来查找订阅方法 + findUsingReflectionInSingleClass(findState); + } + //进入父类查找订阅方法 + findState.moveToSuperclass(); + } + //回收处理findState,并返回订阅方法的List集合 + return getMethodsAndRelease(findState); + } + + + private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { + //获取订阅方法中的订阅事件 + Class eventType = subscriberMethod.eventType; + //创建一个Subscription来保存订阅者和订阅方法 + Subscription newSubscription = new Subscription(subscriber, subscriberMethod); + //获取当前订阅事件中Subscription的List集合 + CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType); + if (subscriptions == null) { + //该事件对应的Subscription的List集合不存在,则重新创建并保存在subscriptionsByEventType中 + subscriptions = new CopyOnWriteArrayList<>(); + subscriptionsByEventType.put(eventType, subscriptions); + } else { + //判断是订阅者是否已经被注册 + if (subscriptions.contains(newSubscription)) { + throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + + eventType); + } + } + + //将newSubscription按照订阅方法的优先级插入到subscriptions中 + int size = subscriptions.size(); + for (int i = 0; i <= size; i++) { + if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { + subscriptions.add(i, newSubscription); + break; + } + } + + //通过订阅者获取该订阅者所订阅事件的集合 + List> subscribedEvents = typesBySubscriber.get(subscriber); + if (subscribedEvents == null) { + subscribedEvents = new ArrayList<>(); + typesBySubscriber.put(subscriber, subscribedEvents); + } + //将当前的订阅事件添加到subscribedEvents中 + subscribedEvents.add(eventType); + //粘性事件的处理,在这里不做详细分析 + if (subscriberMethod.sticky) { + if (eventInheritance) { + // Existing sticky events of all subclasses of eventType have to be considered. + // Note: Iterating over all events may be inefficient with lots of sticky events, + // thus data structure should be changed to allow a more efficient lookup + // (e.g. an additional map storing sub classes of super classes: Class -> List). + Set, Object>> entries = stickyEvents.entrySet(); + for (Map.Entry, Object> entry : entries) { + Class candidateEventType = entry.getKey(); + if (eventType.isAssignableFrom(candidateEventType)) { + Object stickyEvent = entry.getValue(); + checkPostStickyEventToSubscription(newSubscription, stickyEvent); + } + } + } else { + Object stickyEvent = stickyEvents.get(eventType); + checkPostStickyEventToSubscription(newSubscription, stickyEvent); + } + } + } + + + 在这个方法中便是订阅者真正的注册过程。首先会根据subscriber和subscriberMethod来创建一个Subscription对象。之后根据事件类型获取或创建一个Subscription集合subscriptions并添加到typesBySubscriber对象中。最后将刚才创建的Subscription对象添加到subscriptions之中。于是这样就完成了一次订阅者的注册操作 + + + + +#### 4.Post 方法 : +1. 通过ThreadLocal获取各自线程的PostingThreadState,所以同一线程使用同一个eventQueue + +2. PostingThreadState: 保存正在被邮递的事件 + +3. 将event 添加到PostingThreadState的eventQueue + +4. 判断是否正在邮递,因为每个线程只有一个PostingThreadState,当前线程只能有一个在处理 +开始邮递 + 5. 判断当前线程是否是主线程:postingState.isMainThread + +6. 通过事件类型查找所有方法 List> eventTypes = lookupAllEventTypes(eventClass); + +7. postSingleEventForEventType + + // 事件类型 查找 订阅者 Subscription + CopyOnWriteArrayList subscriptions; + synchronized (this) { + subscriptions = subscriptionsByEventType.get(eventClass); + } + + + Subscription { + + Object 包含订阅者对象subscriber + + SubscriberMethod 订阅者订阅方法,注解参数 + + + } + + + +8. postToSubscription(Subscription subscription, Object event, boolean isMainThread) 将事件邮递给 邮递给订阅者 +isMainThread 参数是判断当前是否是主线程 +1. 如果是主线程 直接调用 invokeSubscriber(subscription, event) + 直接通过反射 调用.invoke传入对象和参数 + subscription.subscriberMethod.method.invoke(subscription.subscriber, event); + + + private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { + + Log.i("qy","postToSubscription处理线程切换问题"); + switch (subscription.subscriberMethod.threadMode) { + case POSTING: + // 不切换线程,post和订阅者方法在同一个线程执行 + invokeSubscriber(subscription, event); + break; + case MAIN: + Log.i("qy","订阅方法需要在主线程"); + if (isMainThread) { + Log.i("qy","post方法在主线程,直接反射去掉用"); + invokeSubscriber(subscription, event); + } else { + Log.i("qy","post方法在子线程,通过Handler切换到主线程"); + mainThreadPoster.enqueue(subscription, event); + } + break; + case MAIN_ORDERED: + Log.i("qy","订阅方法需要在主线程排队执行"); + if (mainThreadPoster != null) { + Log.i("qy","开始排队执行"); + mainThreadPoster.enqueue(subscription, event); + } else { + Log.i("qy","反射立刻执行"); + // temporary: technically not correct as poster not decoupled from subscriber + invokeSubscriber(subscription, event); + } + break; + case BACKGROUND: + Log.i("qy","订阅者需要再 后台执行 BACKGROUND,可以允许排队执行"); + if (isMainThread) { + Log.i("qy","Post在主线程,所以喜欢转换子线程执行,并且排队执行"); + backgroundPoster.enqueue(subscription, event); + } else { + Log.i("qy","post在子线程,所以可以立即执行"); + invokeSubscriber(subscription, event); + } + break; + case ASYNC: + Log.i("qy","订阅者需要再 后台执行 不需要排队,直接交给线程池异步执行"); + asyncPoster.enqueue(subscription, event); + break; + default: + throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); + } + } + + + +#### 5. Unregister 方法干了什么 + + + /** Unregisters the given subscriber from all event classes. */ + public synchronized void unregister(Object subscriber) { + List> subscribedTypes = typesBySubscriber.get(subscriber); + if (subscribedTypes != null) { + for (Class eventType : subscribedTypes) { + unsubscribeByEventType(subscriber, eventType); + } + typesBySubscriber.remove(subscriber); + } else { + logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); + } + } + + /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */ + private void unsubscribeByEventType(Object subscriber, Class eventType) { + List subscriptions = subscriptionsByEventType.get(eventType); + if (subscriptions != null) { + int size = subscriptions.size(); + for (int i = 0; i < size; i++) { + Subscription subscription = subscriptions.get(i); + if (subscription.subscriber == subscriber) { + subscription.active = false; + subscriptions.remove(i); + i--; + size--; + } + } + } + } + + +也就是说,unregister 先通过传过来的订阅者找个所有订阅事件类型,比如一个LoginActivity订阅者,里面有很多有参数为 FindEvent LoginEvent 等等,这里也就是先前在订阅的时候保存的集合typesBySubscriber开始派上用场了,接着 通过事件类型 去subscriptionsByEventType 查找所有订阅者,并且判断订阅者等于当前LoginActivity这个,然后remove,同时也要typesBySubscribler 也要remove + + + + +#### 技术点:ThreadLocal保证多线程不干扰, + + CopyOnWriteArrayList 保证多线程安全,提高效率,通过复制,改变引用的方式, + 双校验单例模式并使用voliate 阻止重排序问题 + + + + +BackgroundPoster:后台发布人 + + private final PendingPostQueue queue; + private final EventBus eventBus; + private volatile boolean executorRunning; + + BackgroundPoster implements Runnable, Poster + + enqueue 方法: + 1. 从pendingPostPool中获取一个PendingPost 对象并赋予新的属性值 + 2.将PendingPost添加到PendingPostQueue队列,这是一个同步代码块 + 3.判断一下线程池是否运行,运行就等待,否者就 + +Poster 发布人 + void enqueue(Subscription subscription, Object event); + + + +PendingPost 是一个链表,具有3个属性,事件,订阅者和next,pendingPostPool用来作为PendingPost缓存,也就是 + +private final static List pendingPostPool = new ArrayList(); + +Object event; +Subscription subscription; +PendingPost next; diff --git a/sources/fifo.md b/sources/fifo.md new file mode 100644 index 0000000..e2b0c45 --- /dev/null +++ b/sources/fifo.md @@ -0,0 +1,13 @@ +### LinkHashMap 实现FIFO + + +- FIFO是First Input First Output的缩写,也就是常说的先入先出,默认情况下LinkedHashMap就是按照添加顺序保存,我们只需重写下removeEldestEntry方法即可轻松实现一个FIFO缓存,简化版的实现代码如下 + + + final int cacheSize = 5; + LinkedHashMap lru = new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > cacheSize; + } + }; \ No newline at end of file diff --git a/sources/foreach.md b/sources/foreach.md new file mode 100644 index 0000000..97e7c52 --- /dev/null +++ b/sources/foreach.md @@ -0,0 +1,59 @@ +### Java foreach原理 + + +1. 常规写法 + + + for(int i=0; i list = new ArrayList(); + for(String e : list){ + // + } + +3. foreach原理 + + 1. 对于list集合 + + List a = new ArrayList(); + a.add("1"); + a.add("2"); + a.add("3"); + + for(String temp : a){ + System.out.print(temp); + } + 反编译: + List a = new ArrayList(); + a.add("1"); + a.add("2"); + a.add("3"); + String temp; + for(Iterator i$ = a.iterator(); i$.hasNext(); System.out.print(temp)){ + temp = (String)i$.next(); + } + + 2. 遍历数组 + + String[] arr = {"1","2"}; + for(String e : arr){ + System.out.println(e); + } + + 反编译后代码: + + String arr[] = { "1", "2" }; + String arr$[] = arr; + int len$ = arr$.length; + for(int i$ = 0; i$ < len$; i$++) + { + String e = arr$[i$]; + System.out.println(e); + } + + 总结,遍历集合是对应的集合必须实现Iterator接口,遍历数组直接转成for i的形式 \ No newline at end of file diff --git a/sources/fragment_lazy_load.md b/sources/fragment_lazy_load.md new file mode 100644 index 0000000..aee0fce --- /dev/null +++ b/sources/fragment_lazy_load.md @@ -0,0 +1,7 @@ +### Fragment 懒加载 + + +1.在onCreate方法中判断getUserVisibleHint()判断当前是否显示,然后进行拉数据更细 + +2. 如果不更新UI可以把拉去数据逻辑写在setUserVisibleHint(boolean isVisibleToUser)方法li +因为这个方法早与onCreate,所以如果数据秒回更新Ui会有问题 diff --git a/sources/frame.md b/sources/frame.md new file mode 100644 index 0000000..aa50d46 --- /dev/null +++ b/sources/frame.md @@ -0,0 +1,26 @@ +### Android应用架构设计 + +没有最好的组件,只有更适合业务场景的组件 + +整个项目分为三层,从下往上分别是: + +1. Basic Component Layer: 基础组件层,顾名思义就是一些基础组件,包含了各种开源库以及和业务无关的各种自研工具库; + (okhttp3,RxJava,Retrofit,RxAndroid,LeakCanary,路由组件,Glide,第三方的库)和(自研的一些库比如UIWidget,CommonUtil,InjectView,ActionLog) + + +2. Business Component Layer: 业务组件层(这一层一个有可以没有,大型项目肯定会有),这一层的所有组件都是业务相关的; +(例如支付组件,推送组件(自己websocket实现的),公共登录组件等(可以给多个module用,比如开发两款产品),H5组件,视频组件,文件组件) + + +3. Business Module Layer: 业务 Module 层,在 Android Studio 中每块业务对应一个单独的 Module。 +(钱包模块,商城模块,动态模块,收发快递模块) + + +4. App壳将各个组件组装协作 + + + +然后每个模块使用MVP模式 + + +也可以采用插件化 \ No newline at end of file diff --git a/sources/glide.md b/sources/glide.md new file mode 100644 index 0000000..f94d96e --- /dev/null +++ b/sources/glide.md @@ -0,0 +1,261 @@ +### Glide 源码分析 + +1. Glide.with(context) 调用GlideBuilder创建一个Glide ,最终返回一个RequestManager +2. GlideBuilder.build 会创建这些 + + return new Glide( + context, + engine, + memoryCache, + bitmapPool, + arrayPool, + requestManagerRetriever, + connectivityMonitorFactory, + logLevel, + defaultRequestOptions.lock()); + } + +3. RequestManager.load()方法, + + + // RequestManager 380行 通过前面创建的glide 创建个RequestBuilder + public RequestBuilder as(Class resourceClass) { + return new RequestBuilder<>(glide, this, resourceClass); + } + + // RequestBuilder 190行 + + @SuppressWarnings("unchecked") + public RequestBuilder load(@Nullable Object model) { + return loadGeneric(model); + } + private RequestBuilder loadGeneric(@Nullable Object model) { + this.model = model; + isModelSet = true; + return this; + } + +4. RequestBuilder.apply 配置请求参数 + +5. RequestBuilder.into + + + //RequestBuilder 381 通过GlideContext 封装Target + public Target into(ImageView view) { + ..... + return into(context.buildImageViewTarget(view, transcodeClass)); + } + + // RequestBuilder 349行 + public > Y into(@NonNull Y target) { + Util.assertMainThread(); + Preconditions.checkNotNull(target); + if (!isModelSet) { + throw new IllegalArgumentException("You must call #load() before calling #into()"); + } + + Request previous = target.getRequest(); + + if (previous != null) { + requestManager.clear(target); + } + + requestOptions.lock(); + Request request = buildRequest(target); + target.setRequest(request); + requestManager.track(target, request); + + return target; + } + 6. RequestBuilder 633行 构建request SingleRequest + + private Request obtainRequest(Target target, + RequestOptions requestOptions, RequestCoordinator requestCoordinator, + TransitionOptions transitionOptions, Priority priority, + int overrideWidth, int overrideHeight) { + requestOptions.lock(); + + return SingleRequest.obtain( + context, + model, + transcodeClass, + requestOptions, + overrideWidth, + overrideHeight, + priority, + target, + requestListener, + requestCoordinator, + context.getEngine(), + transitionOptions.getTransitionFactory()); + } + + 7. RequestManager 446 发送请求 + + + void track(Target target, Request request) { + targetTracker.track(target); + requestTracker.runRequest(request); + } + + + 8. SingleRequest.runRequest + + // 399行 + onSizeReady() + + engine.load() + + 9. Engine load() + // 212 行,开启一个线程 + engineJob.start(decodeJob); + + 10. EngineJob start() 行 + + + public void start(DecodeJob decodeJob) { + this.decodeJob = decodeJob; + // 过去Glide线程池,核心线程是1,最大线程4,Keep alive 10秒 + GlideExecutor executor = decodeJob.willDecodeFromCache() + ? diskCacheExecutor + : getActiveSourceExecutor(); + executor.execute(decodeJob); + } + + 11. DecodeJob run() 212行 + + + - 调用 runWrapped 244 + + + + private void runWrapped() { + switch (runReason) { + case INITIALIZE: + stage = getNextStage(Stage.INITIALIZE); + currentGenerator = getNextGenerator(); + runGenerators(); + break; + case SWITCH_TO_SOURCE_SERVICE: + runGenerators(); + break; + case DECODE_DATA: + decodeFromRetrievedData(); + break; + default: + throw new IllegalStateException("Unrecognized run reason: " + runReason); + } + } + + - 调用 runGenerators 277 + + + private void runGenerators() { + currentThread = Thread.currentThread(); + startFetchTime = LogTime.getLogTime(); + boolean isStarted = false; + while (!isCancelled && currentGenerator != null + && !(isStarted = currentGenerator.startNext())) { + stage = getNextStage(stage); + currentGenerator = getNextGenerator(); + + if (stage == Stage.SOURCE) { + reschedule(); + return; + } + } + // We've run out of stages and generators, give up. + if ((stage == Stage.FINISHED || isCancelled) && !isStarted) { + notifyFailed(); + } + + // Otherwise a generator started a new load and we expect to be called back in + // onDataFetcherReady. + } + + 12. DataFetcherGenerator ResourceCacheGenerator startNext() + // 从磁盘缓存中查找 + cacheFile = helper.getDiskCache().get(currentKey); + + + + // 然后放到activiteResource 里面 + // 但是用完后,再放入LruCache 里面 + + + +#### activeResources写入 + // Engine 283 + + @Override + public void onEngineJobComplete(Key key, EngineResource resource) { + Util.assertMainThread(); + // A null resource indicates that the load failed, usually due to an exception. + if (resource != null) { + resource.setResourceListener(key, this); + if (resource.isCacheable()) { + activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue())); + } + } + jobs.remove(key); + } + +#### EngineResource private int acquired;用来做引用计数 + +#### 当引用计数为0的时候会将图片放到LruCache中。 Engine 313 + + + @Override + public void onResourceReleased(Key cacheKey, EngineResource resource) { + Util.assertMainThread(); + activeResources.remove(cacheKey); + if (resource.isCacheable()) { + cache.put(cacheKey, resource); + } else { + resourceRecycler.recycle(resource); + } + } + + + +当滑动图片列表的时候,系统会根据需要将这些图片资源给回收掉,所以activeResources.get(key).get()得到的就会为空,为空也没有必要添加到LruCache中了。 +但是这样子一来,activeResources中就会有很多没有用的项了,而它们又没有被移除掉。为了解决MessageQueue.IdleHandler这个问题。 +(IdleHandler也可以用来解决App启动延时加载的问题,具体可以看Android启动优化之延时加载) + + + + private static class RefQueueIdleHandler implements MessageQueue.IdleHandler { + private final Map>> activeResources; + private final ReferenceQueue> queue; + + public RefQueueIdleHandler(Map>> activeResources, + ReferenceQueue> queue) { + this.activeResources = activeResources; + this.queue = queue; + } + + @Override + public boolean queueIdle() { + // 被回收掉的要移除 + ResourceWeakReference ref = (ResourceWeakReference) queue.poll(); + if (ref != null) { + activeResources.remove(ref.key); + } + + return true; + } + } + + + +Glide加载默认情况下可以分为三级缓存,哪三级呢?他们分别是内存、磁盘和网络。 + + +默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存: + +1.活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片 +2.内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中 +3.资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存 +4.数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存 + + diff --git a/sources/handle_leak.md b/sources/handle_leak.md new file mode 100644 index 0000000..5dffda9 --- /dev/null +++ b/sources/handle_leak.md @@ -0,0 +1,119 @@ +### Handler内存泄露原理与解决 + + + public class LeakCanaryActivity extends AppCompatActivity + + private Handler mHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + } + }; + + Message message = Message.obtain(); + message.what = 1; + mHandler.sendMessageDelayed(message,10*60*1000); + } + + } + +这段代码的逻辑很简单,mHandler延时了10分钟发送消息,类似的代码在我们的项目中也经常出现,但是这样的代码会出现一个问题。 + +1.问题 + + 我们在项目中集成 Square 的开源库 LeakCanary,有关这个库的介绍及使用请看:Github.LeakCanary。 + + 我们首先打开 LeakCanaryActivity ,然后按返回键将这个Activity finish 掉。等待几秒屏幕上会弹出提醒和通知,这说明此时发生了内存泄露的现象。 + +2.原因 + + 究竟是什么时候发生了内存泄露的问题呢? + + 我们知道在Java中,非静态内部类会隐性地持有外部类的引用,二静态内部类则不会。在上面的代码中,Message在消息队列中延时了10分钟,然后才处理该消息。而这个消息引用了Handler对象,Handler对象又隐性地持有了Activity的对象,当发生GC是以为 message – handler – acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露的问题。 + + 危害 + + 众所周知,内存泄露在 Android 开发中是一个比较严重的问题,系统给每一个应用分配的内存是固定的,一旦发生了内存泄露,就会导致该应用可用内存越来越小,严重时会发生 OOM 导致 Force Close。 + +3.这个问题该如何解决呢? + +使用弱引用 + +首先我们需要理解一下相关概念: + +强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 +软应用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 +弱引用:弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 +用更直白的语言描述就是,java对于 强引用 的对象,就绝不收回,对于 软引用 的对象,是能不收回就不收回,这里的能不收回就是指内存足够的情况,对于 弱引用 的对象,是发现就收回,但是一般情况下不会发现。 + +很显然,出现内存泄露问提的原因,就是 Handler 对 Activity 是强引用,导致 GC 在回收 Activity 时无法回收。为了解决这个问题,我们可以把 Handler 对 Activity 弱引用,这样 GC 就能把 Activity 及时的回收,从而杜绝了内存泄露的问题。 + + public class NoLeakActivity extends AppCompatActivity { + + private NoLeakHandler mHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mHandler = new NoLeakHandler(this); + + Message message = Message.obtain(); + + mHandler.sendMessageDelayed(message,10*60*1000); + } + + private static class NoLeakHandler extends Handler{ + private WeakReference mActivity; + + public NoLeakHandler(NoLeakActivity activity){ + mActivity = new WeakReference<>(activity); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + } + } + } + + +2.及时清除消息 + + 在原因中我们说到,正是因为被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,导致 Activity 的泄露。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除,从源头上解除了这条引用链,从而使 Activity 能被及时的回收。 + + public class LeakCanaryActivity extends AppCompatActivity { + + private Handler mHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + } + }; + + Message message = Message.obtain(); + message.what = 1; + mHandler.sendMessageDelayed(message,10*60*1000); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mHandler.removeCallbacksAndMessages(null); + } + } + diff --git a/sources/hash_confict.md b/sources/hash_confict.md new file mode 100644 index 0000000..102c78d --- /dev/null +++ b/sources/hash_confict.md @@ -0,0 +1,5 @@ +### Hash 冲突 + +1. Hash冲突是指 不同的key 计算出同样的HashCode,此时HashMap通过链表的形式,将冲突的元素 + +插入到链表头部,这个链表保存的是Entry \ No newline at end of file diff --git a/sources/hotfix.md b/sources/hotfix.md new file mode 100644 index 0000000..86c5345 --- /dev/null +++ b/sources/hotfix.md @@ -0,0 +1,29 @@ +### 热修复技术和原理 + + +Dex的热修复总结 +Dex的热修复目前来看基本上有四种方案: + +阿里系的从native层入手,见AndFix +QQ空间的方案,插桩,见安卓App热补丁动态修复技术介绍 +微信的方案,见微信Android热补丁实践演进之路,dexDiff和dexPatch,方法很牛逼,需要全量插入,但是这个全量插入的dex中需要删除一些过早加载的类,不然同样会报class is pre verified异常,还有一个缺点就是合成占内存和内置存储空间。微信读书的方式和微信类似,见Android Patch 方案与持续交付,不过微信读书是miniloader方式,启动时容易ANR,在我锤子手机上变现出来特别明显,长时间的卡图标现象。 +美团的方案,也就是instant run的方案,见Android热更新方案Robust + + +#### 腾讯系 +1. QZone +QQ空间超级补丁技术,基于虚拟机class loader动态加载,大致的过程就是:把BUG方法修复以后,放到一个单独的DEX里,应用启动后,将补丁dex插入到class loader的dexElements数组的最前面,让虚拟机优先去加载修复完后的方法。 + + +2. Tinker +微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,区别在于不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX,达到修复的目的。 + + + +#### Tinker原理: + +新dex与旧dex通过dex差分算法生成差异包 patch.dex +将patch dex下发到客户端,客户端将patch dex与旧dex合成为新的全量dex +将合成后的全量dex 插入到dex elements前面(此部分和QQ空间机制类似),完成修复 +可见,Tinker和QQ空间方案最大的不同是,Tinker 下发新旧DEX的差异包,然后将差异包和旧包合成新dex之后进行dex的全量替换,这样也就避免了QQ空间中的插桩操作。然后我们详细看下每一个流程的详细实现细节。 +Tinker的差量包patch.dex是如何生成的,Tinker生成新旧dex的差异包使用了微信自研的dexdiff算法,dexdiff算法是基于dex文件的结构来设计的,首先我们详细看一下dex文件结构: diff --git a/sources/http.md b/sources/http.md new file mode 100644 index 0000000..8d6852c --- /dev/null +++ b/sources/http.md @@ -0,0 +1,107 @@ +### Http请求过程 + + +一次完整的HTTP请求过程 + + +当我们在web浏览器的地址栏中输入: www.baidu.com,然后回车,到底发生了什么 + +过程概览 +  1.对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址 + +  2.根据这个IP,找到对应的服务器,发起TCP的三次握手 + +  3.建立TCP连接后发起HTTP请求 + +  4.服务器响应HTTP请求,浏览器得到html代码 + +  5.浏览器解析html代码,并请求html代码中的资源(如js、css图片等)(先得到html代码,才能去找这些资源) + +  6.浏览器对页面进行渲染呈现给用户 + +注:1.DNS域名解析采用的是递归查询的方式,过程是,先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器 + +2.为什么HTTP协议要基于TCP来实现? TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,会重传) + +3.最后一步浏览器是如何对页面进行渲染的? a)解析html文件构成 DOM树,b)解析CSS文件构成渲染树, c)边解析,边渲染 , d)JS 单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载 + + + +下面我们来详细看看这几个过程的具体细节: +  1.域名解析 +  a)首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存) + +  b)如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的DNS缓存 + +  c)如果还没有找到,那么尝试从 hosts文件里面去找 + +  d)在前面三个过程都没获取到的情况下,就递归地去域名服务器去查找,具体过程如下 + + + + + +DNS优化:两个方面:DNS缓存、DNS负载均衡 + + + +  2.TCP连接(三次握手) +  拿到域名对应的IP地址之后,User-Agent(一般指浏览器)会以一个随机端口(1024<端口<65535)向服务器的WEB程序(常用的有httpd,nginx)等的80端口。这个连接请求(原始的http请求经过TCP/IP4层模型的层层封包)到达服务器端后(这中间有各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的TCP/IP协议栈(用于识别连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终达到WEB程序,最终建立了TCP/IP的连接 + +图解: + + + + + +具体可以翻阅前面关于 TCP三次握手和四次挥手的博客 + + + +  3.建立TCP连接之后,发起HTTP请求 +  HTTP请求报文由三部分组成:请求行,请求头和请求正文 + +  请求行:用于描述客户端的请求方式,请求的资源名称以及使用的HTTP协议的版本号(例:GET/books/java.html HTTP/1.1) + +  请求头:用于描述客户端请求哪台主机,以及客户端的一些环境信息等 + +  注:这里提一个请求头 Connection,Connection设置为 keep-alive用于说明 客户端这边设置的是,本次HTTP请求之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP建立连接的时间 + +  请求正文:当使用POST, PUT等方法时,通常需要客户端向服务器传递数据。这些数据就储存在请求正文中(GET方式是保存在url地址后面,不会放到这里) + + + +  4.服务器端响应http请求,浏览器得到html代码 +  HTTP响应也由三部分组成:状态码,响应头和实体内容 + +  状态码:状态码用于表示服务器对请求的处理结果 + +  列举几种常见的:200(没有问题) 302(要你去找别人) 304(要你去拿缓存) 307(要你去拿缓存) 403(有这个资源,但是没有访问权限) 404(服务器没有这个资源) 500(服务器这边有问题) + +  若干响应头:响应头用于描述服务器的基本信息,以及客户端如何处理数据 + +  实体内容:服务器返回给客户端的数据 + +  注:html资源文件应该不是通过 HTTP响应直接返回去的,应该是通过nginx通过io操作去拿到的吧 + + + +  5.浏览器解析html代码,并请求html代码中的资源 +  浏览器拿到html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载(会使用多线程下载,每个浏览器的线程数不一样),这是时候就用上 keep-alive特性了,建立一次HTTP连接,可以请求多个资源,下载资源的顺序就是按照代码里面的顺序,但是由于每个资源大小不一样,而浏览器又是多线程请求请求资源,所以这里显示的顺序并不一定是代码里面的顺序。 + + + +  6.浏览器对页面进行渲染呈现给用户 +  最后,浏览器利用自己内部的工作机制,把请求的静态资源和html代码进行渲染,渲染之后呈现给用户 + +   浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)。DOM节点中的各个元素都是以盒模型的形式存在,这些都需要浏览器去计算其位置和大小等,这个过程称为relow;当盒模型的位置,大小以及其他属性,如颜色,字体,等确定下来之后,浏览器便开始绘制内容,这个过程称为repain。页面在首次加载时必然会经历reflow和repain。reflow和repain过程是非常消耗性能的,尤其是在移动设备上,它会破坏用户体验,有时会造成页面卡顿。所以我们应该尽可能少的减少reflow和repain。 + +  JS的解析是由浏览器中的JS解析引擎完成的。JS是单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载 + + + +  自此一次完整的HTTP事务宣告完成. + + +总结: +  域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户 \ No newline at end of file diff --git a/sources/imagedownload.md b/sources/imagedownload.md new file mode 100644 index 0000000..2a1a4a9 --- /dev/null +++ b/sources/imagedownload.md @@ -0,0 +1,58 @@ +### 图片下载原理 + + +1. 使用OKHttpClient或HttpConnection 发送一个网络请求,返回一个inputStream 转化为byte数组, +通过BitmapFactory 将byte数组转化成bitmap,此时可以直接使用,也可以存储到本地 + + + /** + * Get data from stream + * @param inStream + * @return byte[] + * @throws Exception + */ + public static byte[] readStream(InputStream inStream) throws Exception{ + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while( (len=inStream.read(buffer)) != -1){ + outStream.write(buffer, 0, len); + } + outStream.close(); + inStream.close(); + return outStream.toByteArray(); + } + + + + // 使用OKHttp就可以通过response.body获取 + + + + private void asyncGet() { + client = new OkHttpClient(); + final Request request = new Request.Builder().get() + .url(IMAGE_URL) + .build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + + Message message = handler.obtainMessage(); + if (response.isSuccessful()) { + message.what = IS_SUCCESS; + message.obj = response.body().bytes(); + handler.sendMessage(message); + } else { + handler.sendEmptyMessage(IS_FAIL); + } + } + }); + } \ No newline at end of file diff --git a/sources/iterationAndroidrecursion.md b/sources/iterationAndroidrecursion.md new file mode 100644 index 0000000..88714df --- /dev/null +++ b/sources/iterationAndroidrecursion.md @@ -0,0 +1,20 @@ +#### 迭代算法和递归算法区别 + +举个例子:我想求1+2+3+4+..+100的值。 +迭代的做法:从1到100,顺着往下累加。1+2=3,3+3=6,6+4=10,10+5=15…… + 程序表示, + int i=1,sum=0; + while(i<=100){ + sum = sum +i; + } +递归的做法:我要求1到100的累加值,如果我已经得到1到99的累加值,将这个值加上100就是1到100的累加值;要得到1到99的累加值,如果已经得到1到98的累加值,将这个值加上99,就是1到99的累加值……最后我要得到1到2的累加值,我如果得到1自身累加值,再加上2即可,1自身的累加值显然就是1了。于是现在我们得到了1到2的累加值,将这个值加3就得到了1到3的累加值,……最后直到得到1到100的累加值。 + 程序表示,其中函数会调用自身,这就是递归方法的典型特征 + int GetSum(int n) + { + if(n<=0) return 0; + else return n+GetSum(n-1); + } + +上述例子中,其实递归最后得到结果也是用迭代方法完成的,只是在程序的处理上直观看不出来。两者都能很好的完成计算任务, + +不同之处在于思维方式上,从而导致不同的计算方法:迭代是正向思维,从头到尾思考问题;递归是逆向思维,他假设我们已经得到了部分结果(假设我已经知道了1到99的累加值,把这个值加上100我们就得到了1到100的累加值了),从尾部追溯到头部,从而让问题简化(当然这个例子中看不出来,这里只是方便理解) \ No newline at end of file diff --git a/sources/java8.md b/sources/java8.md new file mode 100644 index 0000000..f720aa7 --- /dev/null +++ b/sources/java8.md @@ -0,0 +1,821 @@ +### Java8 十大特性 + +JAVA8 十大新特性详解 +2017年03月30日 22:14:17 +阅读数:13345 +一、接口的默认方法 +在接口中新增了default方法和static方法,这两种方法可以有方法体 +1、static方法 +示例代码: +public interface DefalutTest { + static int a =5; + default void defaultMethod(){ + System.out.println("DefalutTest defalut 方法"); + } + + int sub(int a,int b); + + static void staticMethod() { + System.out.println("DefalutTest static 方法"); + } +} + +接口里的静态方法,即static修饰的有方法体的方法不会被继承或者实现,但是静态变量会被继承 +例如:我们添加一个接口DefalutTest的实现类DefaultTestImpl +public class DefaultTestImpl implements DefalutTest{ + + @Override + public int sub(int a, int b) { + // TODO Auto-generated method stub + return a-b; + } + +} + +如下图所示是这个实现类中所有可调用的方法: + + +在这些方法里面我们无法找到staticMethod方法,则说明接口中的static方法不能被它的实现类直接使用。但是我们看到了defaultMethod,说明实现类可以直接调用接口中的default方法; +那么如何使用接口中的static方法呢??? +接口.static方法调用,如:DefalutTest.staticMethod(); + public static void main(String[] args) { + DefaultTestImpl dtl = new DefaultTestImpl(); + DefalutTest.staticMethod(); + } + +当我们试图使用接口的子接口去调用父接口的static方法是,我们发现,无法调用,找不到方法: + +结论:接口中的static方法不能被继承,也不能被实现类调用,只能被自身调用 +2、default方法 +准备一个子接口继承DefalutTest接口 +public interface SubTest extends DefalutTest{ + +} + +准备一个子接口的实现类 +public class SubTestImp implements SubTest{ + + @Override + public int sub(int a, int b) { + // TODO Auto-generated method stub + return a-b; + } + +} + + +现在我们创建一个子接口实现类对象,并调用对象中的default方法: +public class Main { + + public static void main(String[] args) { + SubTestImp stl = new SubTestImp(); + stl.defaultMethod(); + + } +} + +执行结果: +DefalutTest defalut 方法 +结论1:default方法可以被子接口继承亦可被其实现类所调用 +现在我们在子接口中重写default方法,在进行调用: +public interface SubTest extends DefalutTest{ + + default void defaultMethod(){ + System.out.println("SubTest defalut 方法"); + } +} + +执行结果:SubTest defalut 方法 +结论2:default方法被继承时,可以被子接口覆写 +现在,我们去除接口间的继承关系,并使得SubTestImp同时实现父接口和子接口,我们知道此时父接口和子接口中存在同名同参数的default方法,这会怎么样? +如下图所示,实现类报错,实现类要求必须指定他要实现那个接口中的default方法 + +结论3:如果一个类实现了多个接口,且这些接口中无继承关系,这些接口中若有相同的(同名,同参数)的default方法,则接口实现类会报错,接口实现类必须通过特殊语法指定该实现类要实现那个接口的default方法 +特殊语法:<接口>.super.<方法名>([参数]) +示例代码: +public class SubTestImp implements SubTest,DefalutTest{ + + @Override + public int sub(int a, int b) { + // TODO Auto-generated method stub + return a-b; + } + + @Override + public void defaultMethod() { + // TODO Auto-generated method stub + DefalutTest.super.defaultMethod(); + } + +} + +使用示例: +//接口代码 + + interface Formula { + double calculate(int a); + default double sqrt(int a) { + return Math.sqrt(a); + } + } + + //实现 + Formula formula = new Formula() { + @Override + public double calculate(int a) { + return sqrt(a * 100); + } + }; + formula.calculate(100); // 100.0 + formula.sqrt(16); // 4.0 + +二、Lambda 表达式 +Lambda表达式可以看成是匿名内部类,使用Lambda表达式时,接口必须是函数式接口 +基本语法: + <函数式接口> <变量名> = (参数1,参数2...) -> { + //方法体 + } + +说明: +(参数1,参数2…)表示参数列表;->表示连接符;{}内部是方法体 +1、=右边的类型会根据左边的函数式接口类型自动推断; +2、如果形参列表为空,只需保留(); +3、如果形参只有1个,()可以省略,只需要参数的名称即可; +4、如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句; +5、形参列表的数据类型会自动推断; +6、lambda不会生成一个单独的内部类文件; +7、lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错; +示例代码: +public interface LambdaTest { + + abstract void print(); +} + +public interface LambdaTest2 { + + abstract void print(String a); +} + +public interface DefalutTest { + + static int a =5; + default void defaultMethod(){ + System.out.println("DefalutTest defalut 方法"); + } + + int sub(int a,int b); + + static void staticMethod() { + System.out.println("DefalutTest static 方法"); + } +} + +public class Main { + + public static void main(String[] args) { + //匿名内部类--java8之前的实现方式 + DefalutTest dt = new DefalutTest(){ + @Override + public int sub(int a, int b) { + // TODO Auto-generated method stub + return a-b; + } + }; + + //lambda表达式--实现方式1 + DefalutTest dt2 =(a,b)->{ + return a-b; + }; + System.out.println(dt2.sub(2, 1)); + + //lambda表达式--实现方式2,省略花括号 + DefalutTest dt3 =(a,b)->a-b; + System.out.println(dt3.sub(5, 6)); + + //测试final + int c = 5; + DefalutTest dt4 =(a,b)->a-c; + System.out.println(dt4.sub(5, 6)); + + //无参方法,并且执行语句只有1条 + LambdaTest lt = ()-> System.out.println("测试无参"); + lt.print(); + //只有一个参数方法 + LambdaTest2 lt1 = s-> System.out.println(s); + lt1.print("有一个参数"); + } +} + +局部变量修改报错如图: + + +若是强行修改也无法编译通过 +Lambda表达式其他特性: +1、引用实例方法: +语法: + <函数式接口> <变量名> = <实例>::<实例方法名> + //调用 + <变量名>.接口方法([实际参数...]) + +将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法; +示例代码: +如我们引用PrintStream类中的println方法。我们知道System类中有一个PrintStream的实例为out,引用该实例方法:System.out::println: +public class Main { + + public static void main(String[] args) { + + LambdaTest2 lt1 = s-> System.out.println(s); + lt1.print("有一个参数"); + + //改写为: + LambdaTest2 lt2 = System.out::println; + lt2.print("实例引用方式调用"); + } +} + +将lt2调用时的实际参数传递给了PrintStream类中的println方法,并调用该方法 +2、引用类方法: +语法: + <函数式接口> <变量名> = <类>::<类方法名称> + //调用 + <变量名>.接口方法([实际参数...]) + +将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法; +示例代码: +我们可以以数组排序方式为例 +public interface LambdaTest3 { + + abstract void sort(List list,Comparator c); +} + +public class Main { + + public static void main(String[] args) { + List list = new ArrayList(); + list.add(50); + list.add(18); + list.add(6); + list.add(99); + list.add(32); + System.out.println(list.toString()+"排序之前"); + LambdaTest3 lt3 = Collections::sort; + lt3.sort(list, (a,b) -> { + return a-b; + }); + System.out.println(list.toString()+"排序之后"); + } +} + + +执行结果: +[50, 18, 6, 99, 32]排序之前 +[6, 18, 32, 50, 99]排序之后 +再来看Comparator接口,它属于函数式接口,所以我们在Comparator入参时,也采取了lambda表达式写法。 +@FunctionalInterface +public interface Comparator { +... +... +... +} + +3、引用类的实例方法: +定义、调用接口时,需要多传递一个参数,并且参数的类型与引用实例的类型一致 +语法: + //定义接口 + interface <函数式接口>{ + <返回值> <方法名>(<类><类名称>,[其他参数...]); + } + <函数式接口> <变量名> = <类>::<类实例方法名> + //调用 + <变量名>.接口方法(类的实例,[实际参数...]) + +将调用方法时的传递的实际参数,从第二个参数开始(第一个参数指定的类的实例),全部传递给引用的方法,执行引用的方法; +示例代码: +public class LambdaClassTest { + + public int add(int a, int b){ + System.out.println("LambdaClassTest类的add方法"); + return a+b; + } +} + +public interface LambdaTest4 { + + abstract int add(LambdaClassTest lt,int a,int b); +} + +public class Main { + + public static void main(String[] args) { + LambdaTest4 lt4 = LambdaClassTest::add; + LambdaClassTest lct = new LambdaClassTest(); + System.out.println(lt4.add(lct, 5, 8)); + } +} + +4、引用构造器方法: +语法: + <函数式接口> <变量名> = <类>:: + //调用 + <变量名>.接口方法([实际参数...]) + +把方法的所有参数全部传递给引用的构造器,根据参数类型自动推断调用的构造器方法; +示例代码: +public interface LambdaTest5 { + + abstract String creatString(char[] c); +} +public class Main { + + public static void main(String[] args) { + LambdaTest5 lt5 = String::new; + System.out.println(lt5.creatString(new char[]{'1','2','3','a'})); + } +} + +根据传入的参数类型,自动匹配构造函数 +三、函数式接口 +如果一个接口只有一个抽象方法,则该接口称之为函数式接口,因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。 +函数式接口可以使用Lambda表达式,lambda表达式会被匹配到这个抽象方法上 +我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的 +示例代码: +@FunctionalInterface +interface Converter { + T convert(F from); +} +Converter converter = (from) -> Integer.valueOf(from); +Integer converted = converter.convert("123"); +System.out.println(converted); // 123 + +五、Lambda 作用域 +在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。 +六、访问局部变量 +我们可以直接在lambda表达式中访问外层的局部变量,但是该局部变量必须是final的,即使没有加final关键字,之后我们无论在哪(lambda表达式内部或外部)修改该变量,均报错。 +七、访问对象字段与静态变量 +lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的; +示例代码: +class Lambda4 { + static int outerStaticNum; + int outerNum; + void testScopes() { + Converter stringConverter1 = (from) -> { + outerNum = 23; + return String.valueOf(from); + }; + Converter stringConverter2 = (from) -> { + outerStaticNum = 72; + return String.valueOf(from); + }; + } +} + +八、访问接口的默认方法 +Predicate接口 +Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非): + public static void main(String[] args) { + Predicate predicate = (s) -> s.length() > 0; + System.out.println(predicate.test("foo")); // true + System.out.println(predicate.negate().test("foo")); // false + Predicate nonNull = Objects::nonNull; + Predicate isNull = Objects::isNull; + Predicate isEmpty = String::isEmpty; + Predicate isNotEmpty = isEmpty.negate(); + System.out.println(nonNull.test(null)); + System.out.println(isNull.test(null)); + System.out.println(isEmpty.test("sss")); + System.out.println(isNotEmpty.test("")); + } + +运行结果: +true +false +false +true +false +false +Function 接口 +Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen): + Function toInteger = Integer::valueOf; + System.out.println(toInteger.apply("123").getClass()); + Function toInteger2 = toInteger.andThen(String::valueOf); + System.out.println(toInteger2.apply("123").getClass()); + +输出: +class java.lang.Integer +class java.lang.String +Supplier 接口 +Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数 +Supplier personSupplier = Person::new; +personSupplier.get(); // new Person + +Consumer 接口 +Consumer 接口表示执行在单个参数上的操作。接口只有一个参数,且无返回值 + Supplier personSupplier = LambdaClassTest::new; + Consumer greeter = (lt) -> System.out.println("Hello, " + lt.getTest()); + greeter.accept(personSupplier.get()); + +Comparator 接口 +Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法: +Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); +Person p1 = new Person("John", "Doe"); +Person p2 = new Person("Alice", "Wonderland"); +comparator.compare(p1, p2); // > 0 +comparator.reversed().compare(p1, p2); // < 0 + +Optional 接口 +Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么: +Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。 +Optional optional = Optional.of("bam"); +optional.isPresent(); // true +optional.get(); // "bam" +optional.orElse("fallback"); // "bam" +optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b" + +Stream 接口 重要!!! +创建stream–通过of方法 +Stream integerStream = Stream.of(1, 2, 3, 5); +Stream stringStream = Stream.of("taobao"); + +创建stream–通过generator方法 +生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象) +Stream.generate(new Supplier() { + + @Override + + public Double get() { + + return Math.random(); + + } + +}); + +Stream.generate(() -> Math.random()); + +Stream.generate(Math::random); + +三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。 +创建stream–通过iterate方法 +也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环 +Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println); +这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。 +通过Collection子类获取Stream + +public interface Collection extends Iterable { + + //其他方法省略 + + default Stream stream() { + + return StreamSupport.stream(spliterator(), false); + + } + +} + +java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。 +Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。 +Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。 +下面的例子展示了是如何通过并行Stream来提升性能: +首先我们创建一个没有重复元素的大表: +int max = 1000000; +List values = new ArrayList<>(max); +for (int i = 0; i < max; i++) { + UUID uuid = UUID.randomUUID(); + values.add(uuid.toString()); +} + +然后我们计算一下排序这个Stream要耗时多久, +串行排序: +long t0 = System.nanoTime(); +long count = values.stream().sorted().count(); +System.out.println(count); +long t1 = System.nanoTime(); +long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); +System.out.println(String.format("sequential sort took: %d ms", millis)); + +// 串行耗时: 899 ms +并行排序: +long t0 = System.nanoTime(); +long count = values.parallelStream().sorted().count(); +System.out.println(count); +long t1 = System.nanoTime(); +long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); +System.out.println(String.format("parallel sort took: %d ms", millis)); + +// 并行排序耗时: 472 ms +上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream(); +stream的其他应用: +1、count()、max()、min()方法 +import java.util.ArrayList; +import java.util.List; + +public class Main { + + public static void main(String[] args) { + List collection = new ArrayList(); + collection.add(14); + collection.add(5); + collection.add(43); + collection.add(89); + collection.add(64); + collection.add(112); + collection.add(55); + collection.add(55); + collection.add(58); + //list长度 + System.out.println(collection.parallelStream().count()); + + //求最大值,返回Option,通过Option.get()获取值 + System.out.println(collection.parallelStream().max((a,b)->{return a-b;}).get()); + + //求最小值,返回Option,通过Option.get()获取值 + System.out.println(collection.parallelStream().min((a,b)->{return a-b;}).get()); + + } +} + + +2、Filter 过滤方法 +过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作。 +import java.util.ArrayList; +import java.util.List; + +public class Main { + + public static void main(String[] args) { + List collection = new ArrayList(); + collection.add(14); + collection.add(5); + collection.add(43); + collection.add(89); + collection.add(64); + collection.add(112); + collection.add(55); + collection.add(55); + collection.add(58); + Long count =collection.stream().filter(num -> num!=null). + filter(num -> num.intValue()>50).count(); + System.out.println(count); + } +} + +3、distinct方法 +去除重复 +import java.util.ArrayList; +import java.util.List; + +public class Main { + + public static void main(String[] args) { + List collection = new ArrayList(); + collection.add(14); + collection.add(5); + collection.add(43); + collection.add(89); + collection.add(64); + collection.add(112); + collection.add(55); + collection.add(55); + collection.add(58); + collection.stream().distinct().forEach(System.out::println);; + } +} + +4、Sort 排序 +排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。 +stringCollection + .stream() + .sorted() + .filter((s) -> s.startsWith("a")) + .forEach(System.out::println); +// "aaa1", "aaa2" + +需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的: +System.out.println(stringCollection); +// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1 + +5、Map 映射 +对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗; +import java.util.ArrayList; +import java.util.List; + +public class Main { + + public static void main(String[] args) { + List collection = new ArrayList(); + collection.add("14"); + collection.add("5"); + collection.add("43"); + collection.add("89"); + collection.add("64"); + collection.add("112"); + collection.add("55"); + collection.add("55"); + collection.add("58"); + //将String转化为Integer类型 + collection.stream().mapToInt(Integer::valueOf).forEach(System.out::println); + //或 + collection.stream().mapToInt(a->Integer.parseInt(a)).forEach(System.out::println); + } +} + +也可以这样用: +List nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10); +System.out.println(“sum is:”+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num * 2). + peek(System.out::println).skip(2).limit(4).sum()); + +7、limit: +对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素; +8、skip: +返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream; +9、Match 匹配 + + Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。 + boolean anyStartsWithA = + stringCollection + .stream() + .anyMatch((s) -> s.startsWith("a")); + System.out.println(anyStartsWithA); // true + boolean allStartsWithA = + stringCollection + .stream() + .allMatch((s) -> s.startsWith("a")); + System.out.println(allStartsWithA); // false + boolean noneStartsWithZ = + stringCollection + .stream() + .noneMatch((s) -> s.startsWith("z")); + System.out.println(noneStartsWithZ); // true + +10、Count 计数 +计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。 +long startsWithB = + stringCollection + .stream() + .filter((s) -> s.startsWith("b")) + .count(); +System.out.println(startsWithB); // 3 + +11、Reduce 规约 +这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的: +Optional reduced = + stringCollection + .stream() + .sorted() + .reduce((s1, s2) -> s1 + "#" + s2); +reduced.ifPresent(System.out::println); +// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2" + +Map +前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。 +Map map = new HashMap<>(); +for (int i = 0; i < 10; i++) { + map.putIfAbsent(i, "val" + i); +} +map.forEach((id, val) -> System.out.println(val)); + +以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。 +下面的例子展示了map上的其他有用的函数: +map.computeIfPresent(3, (num, val) -> val + num); +map.get(3); // val33 +map.computeIfPresent(9, (num, val) -> null); +map.containsKey(9); // false +map.computeIfAbsent(23, num -> "val" + num); +map.containsKey(23); // true +map.computeIfAbsent(3, num -> "bam"); +map.get(3); // val33 + +接下来展示如何在Map里删除一个键值全都匹配的项: +map.remove(3, "val3"); +map.get(3); // val33 +map.remove(3, "val33"); +map.get(3); // null + +另外一个有用的方法: +map.getOrDefault(42, "not found"); // not found + +对Map的元素做合并也变得很容易了: +map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); +map.get(9); // val9 +map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); +map.get(9); // val9concat + +Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。 +steam在实际项目中使用的代码片段: +//1、有list集合生成以productId为key值得map集合 +Map> cartManagerGroup = + carts.stream().collect( + Collectors.groupingBy(CartManager::getProductId) + ); +//2、取得购物车中数量之和 +IntStream is = list.stream().mapToInt((CartManager c)->c.getQuantity()); +is.sum();//数量之和 + +//3、所有订单中商品数量*订单金额求和 +orderDetailsNew.parallelStream() + .mapToDouble(orderDetailMid -> orderDetailMid.getQuantity()*orderDetailMid.getFinalPrice()).sum() + +//4、过滤出指定类型的订单,并生成新的集合 +orderDetails.stream(). + filter(orderDetail -> StringUtil.isEmpty(orderDetail.getPromotionsType())|| !orderDetail.getPromotionsType().equals(PromotionTypeEnum.ORDERGIFTPROMOTION.getType())).collect(Collectors.toList()); + +//5、过滤购物车未被选中商品并生成新的list +carts.stream().filter(cart -> cart.getSelectFlag()==1).collect(Collectors.toList()); + +//6、将list以商品促销委key转化为map +Map> map = + promotionsGiftProducts.stream().collect( Collectors.groupingBy(PromotionsGiftProduct::getPromotionId)); + +//7、从list中分离出只存储productId的列表list +List productIds = needUpdate.parallelStream() + .map(CartManager::getProductId) + .collect(Collectors.toList()); + +九、Date API +Java 8 在包java.time下包含了一组全新的时间日期API。 +Clock 时钟 +Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。 +Clock clock = Clock.systemDefaultZone(); +long millis = clock.millis(); +Instant instant = clock.instant(); +Date legacyDate = Date.from(instant); // legacy java.util.Date + +Timezones 时区 +System.out.println(ZoneId.getAvailableZoneIds()); +// prints all available timezone ids +ZoneId zone1 = ZoneId.of("Europe/Berlin"); +ZoneId zone2 = ZoneId.of("Brazil/East"); +System.out.println(zone1.getRules()); +System.out.println(zone2.getRules()); +// ZoneRules[currentStandardOffset=+01:00] +// ZoneRules[currentStandardOffset=-03:00] + +LocalTime 本地时间 +LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差: +LocalTime now1 = LocalTime.now(zone1); +LocalTime now2 = LocalTime.now(zone2); +System.out.println(now1.isBefore(now2)); // false +long hoursBetween = ChronoUnit.HOURS.between(now1, now2); +long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); +System.out.println(hoursBetween); // -3 +System.out.println(minutesBetween); // -239 + +LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。 +LocalTime late = LocalTime.of(23, 59, 59); +System.out.println(late); // 23:59:59 +DateTimeFormatter germanFormatter = + DateTimeFormatter + .ofLocalizedTime(FormatStyle.SHORT) + .withLocale(Locale.GERMAN); +LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); +System.out.println(leetTime); // 13:37 + +LocalDate 本地日期 +LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。 +LocalDate today = LocalDate.now(); +LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); +LocalDate yesterday = tomorrow.minusDays(2); +LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); +DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); + +System.out.println(dayOfWeek); // FRIDAY + +从字符串解析一个LocalDate类型和解析LocalTime一样简单: +DateTimeFormatter germanFormatter = + DateTimeFormatter + .ofLocalizedDate(FormatStyle.MEDIUM) + .withLocale(Locale.GERMAN); +LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); +System.out.println(xmas); // 2014-12-24 + +LocalDateTime 本地日期时间 +LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。 +LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); +DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); +System.out.println(dayOfWeek); // WEDNESDAY +Month month = sylvester.getMonth(); +System.out.println(month); // DECEMBER +long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); +System.out.println(minuteOfDay); // 1439 + +只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。 +Instant instant = sylvester + .atZone(ZoneId.systemDefault()) + .toInstant(); +Date legacyDate = Date.from(instant); +System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014 + +格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式: +DateTimeFormatter formatter = + DateTimeFormatter + .ofPattern("MMM dd, yyyy - HH:mm"); +LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); +String string = formatter.format(parsed); +System.out.println(string); // Nov 03, 2014 - 07:13 + +和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。 +关于时间日期格式的详细信息: +http://download.java.net/jdk8/docs/api/java/time/format/DateTimeFormatter.html +十、Annotation 注解 +在Java 8中支持多重注解了 \ No newline at end of file diff --git a/sources/javaCopy.md b/sources/javaCopy.md new file mode 100644 index 0000000..77001e3 --- /dev/null +++ b/sources/javaCopy.md @@ -0,0 +1,269 @@ +### Java 克隆详解 + + +1. 浅复制(浅克隆)这种浅复制,其实也就是把被复制的这个对象的一些变量值拿过来了。最后生成student2还是一个新的对象。 + + + public class CloneTest1 + { + public static void main(String[] args) throws Exception + { + Student student = new Student(); + student.setAge(24); + student.setName("niesong"); + Student student2 = (Student)student.clone(); + //这个是调用下面的那个方法,然后把这个这个对象Clone到student + System.out.println("Age:" + student2.getAge() + " " + "Name:" + student2.getName()); + + System.out.println("---------------------"); + student2.setAge(23); + //克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值 + + System.out.println(student.getAge()); + System.out.println(student2.getAge()); + } + + + } +//克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法 + + class Student implements Cloneable + { + private int age; + //定义为private说明这个成员变量只能被被当前类中访问,如果外部需要获得,那么就只能通过getAge方法进行获取 + private String name; + public int getAge() + { + return age; + } + public void setAge(int age) + { + this.age = age; + } + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + @Override + public Object clone() throws CloneNotSupportedException + { + Object object = super.clone(); + return object; + } + } +2. 深复制(情况1使用的是在克隆的时候手动进行深克隆) + + + public class CloneTest2 + { + public static void main(String[] args) throws Exception + { + Teacher teacher = new Teacher(); + teacher.setAge(40); + teacher.setName("teacher zhang"); + + Student2 student2 = new Student2(); + student2.setAge(14); + student2.setName("lisi"); + student2.setTeacher(teacher); + + Student2 student3 = (Student2)student2.clone(); + //这里是深复制,所以这时候Student2中的teacher就是teacher这个对象的一个复制,就和student3是student2的一个复制 + //所以下面teacher.setName只是对他原来的这个对象更改,但是复制的那个并没有更改 + System.out.println(student3.getAge()); + System.out.println(student3.getName()); + System.out.println(student3.getTeacher().getAge()); + teacher.setName("teacher niesong");//不会又任何影响 + System.out.println(student3.getTeacher().getName()); + + } + + } + class Student2 implements Cloneable + { + private int age; + private String name; + private Teacher teacher; + public int getAge() + { + return age; + } + public void setAge(int age) + { + this.age = age; + } + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + public Teacher getTeacher() + { + return teacher; + } + public void setTeacher(Teacher teacher) + { + this.teacher = teacher; + } + @Override + public Object clone() throws CloneNotSupportedException + { + //这一步返回的这个student2还只是一个浅克隆, + Student2 student2 = (Student2)super.clone(); + //然后克隆的过程中获得这个克隆的student2,然后调用这个getTeacher这个方方法得到这个Teacher对象。然后实现克隆。在设置到这个student2中的Teacher。 + //这样实现了双层克隆使得那个teacher对象也得到了复制。 + student2.setTeacher((Teacher)student2.getTeacher().clone()); + //双层克隆使得那个teacher对象也得到了复制 + return student2; + } + } + class Teacher implements Cloneable + { + private int age; + private String name; + public int getAge() + { + return age; + } + public void setAge(int age) + { + this.age = age; + } + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + @Override + public Object clone() throws CloneNotSupportedException + { + return super.clone(); + } + + } +3. 利用serializable实现深复制(这个是利用Serializable,利用序列化的方式来实现深复制(深克隆),在其中利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个复制,然后实现序列化的这个会将引用的那个对象也一并进行深复制,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式) + + + + public class CloneTest3 + { + public static void main(String[] args) throws Exception + { + Teacher3 teacher3 = new Teacher3(); + teacher3.setAge(23); + teacher3.setName("niesong"); + + Student3 student3 = new Student3(); + student3.setAge(50); + student3.setName("wutao"); + student3.setTeacher3(teacher3); + + Student3 ss = (Student3)student3.deepCopt(); + System.out.println(ss.getAge()); + System.out.println(ss.getName()); + + System.out.println("---------------------"); + System.out.println(ss.getTeacher3().getAge()); + System.out.println(ss.getTeacher3().getName()); + + System.out.println("-----------------------"); + + ss.getTeacher3().setAge(7777); + ss.getTeacher3().setName("hhhhh"); + + System.out.println(teacher3.getAge()); + System.out.println(teacher3.getName()); + //虽然上面的已经改了,但是改的是那个复制对象后的那个里面的,然后那个原来的那个里面的并没有改,下面验证::: + + System.out.println("-----------------"); + + System.out.println(ss.getTeacher3().getAge()); + System.out.println(ss.getTeacher3().getName()); + + + + + + } + + + } + class Teacher3 implements Serializable + { + // 上面的那个警告可以直接消除,除了使用在设置中不显示这个警告,还可以使用下面的这两条语句中的任何一条语句 + // 这个serialVersionUID为了让该类别Serializable向后兼容 + // private static final long serialVersionUID = 1L; + // private static final long serialVersionUID = 8940196742313994740L; + private int age; + private String name; + public int getAge() + { + return age; + } + public void setAge(int age) + { + this.age = age; + } + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + } + class Student3 implements Serializable + { + private static final long serialVersionUID = 1L; + private int age; + private String name; + private Teacher3 teacher3; + public int getAge() + { + return age; + } + public void setAge(int age) + { + this.age = age; + } + public String getName() + { + return name; + } + public void setName(String name) + { + this.name = name; + } + public Teacher3 getTeacher3() + { + return teacher3; + } + public void setTeacher3(Teacher3 teacher3) + { + this.teacher3 = teacher3; + } + //使得序列化student3的时候也会将teacher序列化 + public Object deepCopt()throws Exception + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(this); + //将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中 + //有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中 + + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + return ois.readObject(); + //这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆 + } diff --git a/sources/javabasic.md b/sources/javabasic.md new file mode 100644 index 0000000..44012b5 --- /dev/null +++ b/sources/javabasic.md @@ -0,0 +1,12 @@ +### Java基础面试题 + + +1. 基本类型 + + byte 1 + short 2 + char 2 + int 4 + float 4 + double 8 + long 8 \ No newline at end of file diff --git a/sources/javacollection.md b/sources/javacollection.md index 7076d1d..59aef90 100644 --- a/sources/javacollection.md +++ b/sources/javacollection.md @@ -1,5 +1,5 @@ -##Java高级集合中难点 +### Java高级集合中难点 ### WeakHashMap diff --git a/sources/javaconcurrent/atomic.md b/sources/javaconcurrent/atomic.md new file mode 100644 index 0000000..08ae49a --- /dev/null +++ b/sources/javaconcurrent/atomic.md @@ -0,0 +1,271 @@ +#### Atomic原理分析 + +Atomic类 在JDK5.0之前,想要实现无锁无等待的算法是不可能的,除非用本地库,自从有了Atomic变量类后,这成为可能。下面这张图是java.util.concurrent.atomic包下的类结构。 + + + 传统锁的问题 + 我们先来看一个例子:计数器(Counter),采用Java里比较方便的锁机制synchronized关键字,初步的代码如下: + + class Counter { + + private int value; + + public synchronized int getValue() { + return value; + } + + public synchronized int increment() { + return ++value; + } + + public synchronized int decrement() { + return --value; + } + } + + 其实像这样的锁机制,满足基本的需求是没有问题的了,但是有的时候我们的需求并非这么简单,我们需要更有效,更加灵活的机制,synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁,这里会有些问题:首先,如果被阻塞的线程优先级很高很重要怎么办?其次,如果获得锁的线程一直不释放锁怎么办?(这种情况是非常糟糕的)。还有一种情况,如果有大量的线程来竞争资源,那CPU将会花费大量的时间和资源来处理这些竞争(事实上CPU的主要工作并非这些),同时,还有可能出现一些例如死锁之类的情况,最后,其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过于笨重,因此,对于这种需求我们期待一种更合适、更高效的线程安全机制。 + + + 硬件同步策略 + 现在的处理器都支持多重处理,当然也包含多个处理器共享外围设备和内存,同时,加强了指令集以支持一些多处理的特殊需求。特别是几乎所有的处理器都可以将其他处理器阻塞以便更新共享变量。 + + + + Compare and swap(CAS) + 当前的处理器基本都支持CAS,只不过每个厂家所实现的算法并不一样罢了,每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。我们来看一个例子,解释CAS的实现过程(并非真实的CAS实现): + + 以下模拟实现CAS,真实不是这样的,这里只是为了解释 + + class SimulatedCAS { + private int value; + + public synchronized int getValue() { + return value; + } + public synchronized int compareAndSwap(int expectedValue, int newValue) { + int oldValue = value; + if (value == expectedValue) + value = newValue; + return oldValue; + } + } + + 下面是一个用CAS实现的Counter + public class CasCounter { + private SimulatedCAS value; + + public int getValue() { + return value.getValue(); + } + + public int increment() { + int oldValue = value.getValue(); + while (value.compareAndSwap(oldValue, oldValue + 1) != oldValue) + oldValue = value.getValue(); + return oldValue + 1; + } + + + +标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference +数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray +更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater +复合变量类:AtomicMarkableReference,AtomicStampedReference +第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。我们来看个例子,与我们平时i++所对应的原子操作为:getAndIncrement() + + +注意:volatile 可以保证可见性,但不能保证原子性。所以不能保证线程安全 + +CAS线程安全 +说了半天,我们要回归到最原始的问题了:这样怎么实现线程安全呢?请大家自己先考虑一下这个问题,其实我们在语言层面是没有做任何同步的操作的,大家也可以看到源码没有任何锁加在上面,可它为什么是线程安全的呢?这就是Atomic包下这些类的奥秘:语言层面不做处理,我们将其交给硬件—CPU和内存,利用CPU的多处理能力,实现硬件层面的阻塞,再加上volatile变量的特性即可实现基于原子操作的线程安全。所以说,CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效 + + +AtomicBoolean类 getAndSet 方法和 compareAndSet方法的区别,这两个方法的区别在java的文档中记录的很明确了 + +compareAndSet:如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。这里需要注意的是这个方法的返回值实际上是是否成功修改,而与之前的值无关。 + +getAndSet :以原子方式设置为给定值,并返回以前的值。 + + +#### AtomicReference使用? + + AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。在介绍AtomicReference的同时,我希望同时提出一个有关原子操作的逻辑上的不足。 + + 之前我们说过,线程判断被修改对象是否可以正确写入的条件是对象的当前值和期望是否一致。这个逻辑从一般意义上来说是正确的。但有可能出现一个小小的例外,就是当你获得对象当前数据后,在准备修改为新值前,对象的值被其他线程连续修改了2次,而经过这2次修改后,对象的值又恢复为旧值。这样,当前线程就无法正确判断这个对象究竟是否被修改过。如图4.2所示,显示了这种情况。 + + +对象值被反复修改回原数据 一般来说,发生这种情况的概率很小。而且即使发生了,可能也不是什么大问题。比如,我们只是简单得要做一个数值加法,即使在我取得期望值后,这个数字被不断的修改,只要它最终改回了我的期望值,我的加法计算就不会出错。也就是说,当你修改的对象没有过程的状态信息,所有的信息都只保存于对象的数值本身。 + + 但是,在现实中,还可能存在另外一种场景。就是我们是否能修改对象的值,不仅取决于当前值,还和对象的过程变化有关,这时,AtomicReference就无能为力了。 + +打一个比方,如果有一家蛋糕店,为了挽留客户,绝对为贵宾卡里余额小于20元的客户一次性赠送20元,刺激消费者充值和消费。但条件是,每一位客户只能被赠送一次。 + +现在,我们就来模拟这个场景,为了演示AtomicReference,我在这里使用AtomicReference实现这个功能。首先,我们模拟用户账户余额。 + +定义用户账户余额: + + +static AtomicReference money=newAtomicReference(); +// 设置账户初始值小于20,显然这是一个需要被充值的账户 +money.set(19); +   + +接着,我们需要若干个后台线程,它们不断扫描数据,并为满足条件的客户充值。 + + + //模拟多个线程同时更新后台数据库,为用户充值 + for(int i = 0 ; i < 3 ; i++) { + new Thread(){ + publicvoid run() { + while(true){ + while(true){ + Integer m=money.get(); + if(m<20){ + if(money.compareAndSet(m, m+20)){ + System.out.println("余额小于20元,充值成功,余额:"+money.get()+"元"); + break; + } + }else{ + //System.out.println("余额大于20元,无需充值"); + break ; + } + } + } + } + }.start(); + } +   + +上述代码第8行,判断用户余额并给予赠予金额。如果已经被其他用户处理,那么当前线程就会失败。因此,可以确保用户只会被充值一次。 + + 此时,如果很不幸的,用户正好正在进行消费,就在赠予金额到账的同时,他进行了一次消费,使得总金额又小于20元,并且正好累计消费了20元。使得消费、赠予后的金额等于消费前、赠予前的金额。这时,后台的赠予进程就会误以为这个账户还没有赠予,所以,存在被多次赠予的可能。下面,模拟了这个消费线程: + + + + //用户消费线程,模拟消费行为 + new Thread() { + public voidrun() { + for(inti=0;i<100;i++){ + while(true){ + Integer m=money.get(); + if(m>10){ + System.out.println("大于10元"); + if(money.compareAndSet(m, m-10)){ + System.out.println("成功消费10元,余额:"+money.get()); + break; + } + }else{ + System.out.println("没有足够的金额"); + break; + } + } + try{Thread.sleep(100);} catch (InterruptedException e) {} + } + } + }.start(); +  上述代码中,消费者只要贵宾卡里的钱大于10元,就会立即进行一次10元的消费。执行上述程序,得到的输出如下: + + + +余额小于20元,充值成功,余额:39元 +大于10元 +成功消费10元,余额:29 +大于10元 +成功消费10元,余额:19 +余额小于20元,充值成功,余额:39元 +大于10元 +成功消费10元,余额:29 +大于10元 +成功消费10元,余额:39 +余额小于20元,充值成功,余额:39元 + + 从这一段输出中,可以看到,这个账户被先后反复多次充值。其原因正是因为账户余额被反复修改,修改后的值等于原有的数值。使得CAS操作无法正确判断当前数据状态 + + + + +#### 用AtomicStampedReference解决ABA问题 + +线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,例如下面的例子: + + + +现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B: + +head.compareAndSet(A,B); + +在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态: + + + +此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为: + + + +其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。 + +以上就是由于ABA问题带来的隐患,各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记,避免并发操作带来的问题,在Java中,AtomicStampedReference也实现了这个作用,它通过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题,例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新,AtomicInteger会成功执行CAS操作,而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败: + + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.atomic.AtomicStampedReference; + + public class ABA { + private static AtomicInteger atomicInt = new AtomicInteger(100); + private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0); + + public static void main(String[] args) throws InterruptedException { + Thread intT1 = new Thread(new Runnable() { + @Override + public void run() { + atomicInt.compareAndSet(100, 101); + atomicInt.compareAndSet(101, 100); + } + }); + + Thread intT2 = new Thread(new Runnable() { + @Override + public void run() { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + } + boolean c3 = atomicInt.compareAndSet(100, 101); + System.out.println(c3); // true + } + }); + + intT1.start(); + intT2.start(); + intT1.join(); + intT2.join(); + + Thread refT1 = new Thread(new Runnable() { + @Override + public void run() { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + } + atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); + atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); + } + }); + + Thread refT2 = new Thread(new Runnable() { + @Override + public void run() { + int stamp = atomicStampedRef.getStamp(); + try { + TimeUnit.SECONDS.sleep(2); + } catch (InterruptedException e) { + } + boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1); + System.out.println(c3); // false + } + }); + + refT1.start(); + refT2.start(); + } + } \ No newline at end of file diff --git a/sources/javaconcurrent/java_core_semaphore.md b/sources/javaconcurrent/java_core_semaphore.md new file mode 100644 index 0000000..fbd2fe8 --- /dev/null +++ b/sources/javaconcurrent/java_core_semaphore.md @@ -0,0 +1 @@ +### \ No newline at end of file diff --git a/sources/javaconcurrent/program_english.md b/sources/javaconcurrent/program_english.md new file mode 100644 index 0000000..a478081 --- /dev/null +++ b/sources/javaconcurrent/program_english.md @@ -0,0 +1,20 @@ + + +### 比较难读的英文单词 + +- synchronized + +- AsyncTask + +- semaphore + +- reentrantLock + +- concurrentHashMap + +- condition + +- segment + +- volatile + diff --git a/sources/javaconcurrent/selfRotate.md b/sources/javaconcurrent/selfRotate.md new file mode 100644 index 0000000..9849831 --- /dev/null +++ b/sources/javaconcurrent/selfRotate.md @@ -0,0 +1,109 @@ +### CAS自旋锁实现以及应用 + +- CAS 比较交换 compareAndSet,而底层使用了Unsafe调用的 + + public final boolean compareAndSet(int expect, int update) { + return U.compareAndSwapInt(this, VALUE, expect, update); + } + + 类似AtomicInteger里面都是用了compareAndSet + + + 我们想使用CAS实现复杂对象引用可以使用atomicReference + + 并通过compareAndSet实现CAS,如果想实现一个线程安全的操作可以使用 + + + +- 原生的内部自旋实现原理 + + + private final static AtomicBoolean flag = new AtomicBoolean(); + if(flag.compareAndSet(false,true)){ + init(); + } + 比如 AtomicInteger 可以用在计数器中,多线程环境中,保证计数准确。 + + + // 自旋实现,线程是安全的 返回的是旧值 + public final int getAndIncrement() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return current; + } + } + // 自旋实现,线程是安全的 返回的是新值 + public final int incrementAndGet() { + for (;;) { + int current = get(); + int next = current + 1; + if (compareAndSet(current, next)) + return next; + } +##### 实际应用 + + +- 1通过自旋方式实现线程安全可以应用到实际业务中 + + + //多线程无参数可以提高效率,减少线程因为同步机制阻塞,等待占用资源, 而使用CAS,其他线程相当于就可以干别的任务去了 + //如果是有参数的,可以使用final AtomicReference[]> params;将参数存储起来, + //然后参数的存储 + + /** + * 通过这个方法可以比较地址是否被更改过,因为我们用的是数组,并且每次更改都会有个新的数组代替,所以地址会变的 + * internal fun add(rs: Param): Boolean { + while (true) { + val a = params.get() + + val len = a.size + val b = arrayOfNulls>(len + 1) + System.arraycopy(a, 0, b, 0, len) + b[len] = rs + if (params.compareAndSet(a, b)) { + return true + } + } + } + */ + private fun testThread() { + // 旧值不等于0,证明已经发送过, 来一个线程count值就会被+1,相当于计数器+1,同时达到排队模式 + if (count.getAndIncrement() != 0) { + return + } + var missed = 1 + while (true) { //这里使用while循环,当missed 有很多是,让其再次执行 + Thread.sleep(3000) + Log.i("rxjava", "id=" + Thread.currentThread().id) + missed = count.addAndGet(-missed) + if (missed == 0) { + break + } + } + } + +- 2 当我们想根据之前的对象某些值,重新计算一个新的值,然后保存到新的对象中 + + + /** + 这样下面的代码就是线程安全的,但是可能出现ABA情况,解决这个情况可以使用AtomicStampReference + **/ + + //自旋的一种实现方式 + fun updateObject(factory(oldValue)->newValue){ + + for(;;){ + + val oldValue = field.get() + + val newValue = factory() //可以是一个复杂逻辑 + + if(field.compareAndSet(oldValue,newValue)){ + return true + } + + } + + } \ No newline at end of file diff --git a/sources/javageneric.md b/sources/javageneric.md new file mode 100644 index 0000000..310c6fe --- /dev/null +++ b/sources/javageneric.md @@ -0,0 +1,152 @@ +#### Java泛型总结,kotlin也是一样 + + + 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢? + + 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参), + + 然后在使用/调用时传入具体的类型(类型实参)。 + + 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中, + + 操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。 + + +### 方法返回值是个泛型,对返回值的要求任何类型都可以,方法内部使用的泛型由的接受者决定,所以此方法不能单独调用 + + fun customTransformer(): ObservableTransformer { + return ObservableTransformer { upstream -> + upstream.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + } + } + + +### 方法参数泛型,data的类型可以是任何类型,没有做限制 + + +fun getUrl(data:T){ + +} + + +#### 类泛型 + + class Person{ + + } + +#### 泛型限制 + +List 表示List中存放的都是T或者T的子类型 + + List foo = new ArrayList(); + List foo = new ArrayList(); + List foo = new ArrayList(); + +List表示List中存放的都是T或者T的父类型 + + List foo = new ArrayList(); + List foo = new ArrayList(); + List foo = new ArrayList(); + + + 总结 + 参数写成:T,对于这个泛型,?代表容器里的元素类型,由于只规定了元素必须是B的超类,导致元素没有明确统一的“根”(除了Object这个必然的根),所以这个泛型你其实无法使用它,对吧,除了把元素强制转成Object。所以,对把参数写成这样形态的函数,你函数体内,只能对这个泛型做插入操作,而无法读 + + 参数写成: T,由于指定了B为所有元素的“根”,你任何时候都可以安全的用B来使用容器里的元素,但是插入有问题,由于供奉B为祖先的子树有很多,不同子树并不兼容,由于实参可能来自于任何一颗子树,所以你的插入很可能破坏函数实参,所以,对这种写法的形参,禁止做插入操作,只做读取。 + + ? extends T 可以读取到T对象,而不能写入T对象和T的子对象 + + ? super T 可以读取到Object对象,可以写入T对象和T的子对象 + 若想既可以读取又可以写入,则不要用通配符,直接用T + + 类似于消费者生产者模式 + ? extends T 只读, ? super T 只写入 + + + + public class Collections { + public static void copy(List dest, List src) { + for (int i = 0; i < src.size(); i++) + dest.set(i, src.get(i)); + } + } + } + +?表示不确定的 java 类型,通配符其实在声明局部变量时是没有什么意义的 + + List listAnimals 这种局部变量无意义 + + + + static int countLegs (List animals ) { + int retVal = 0; + for ( Animal animal : animals ) + { + retVal += animal.countLegs(); + } + return retVal; + } + + static int countLegs1 (List< Animal > animals ){ + int retVal = 0; + for ( Animal animal : animals ) + { + retVal += animal.countLegs(); + } + return retVal; + } + + public static void main(String[] args) { + List dogs = new ArrayList<>(); + // 不会报错,因为使用了通配符,表示任何Animal子类都可以 + countLegs( dogs ); + // 报错 ,这里就会报错,因为参数接收类型是 List + countLegs1(dogs); + } + + 上界通配符 < ? extends E> + 上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。 + + 下界通配符 < ? super E> + 下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object + + + +### ?和 T 的区别(这里的T可以是任何定义的字母) + + - 通配符在局部变量使用无意义 + + //这里表示 集合可以放入T类型元素 + List = ArrayList(); + //这里表示 集合可以放入任何类型元素,但这样没意义;使用时还得强转;通配符一般使用在方法参数中 + List = ArrayList(); + + + - ?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :、 + + // 可以 + T t = operate(); + // 不可以 + ?car = operate(); + + T extends A + + - 通配符可以使用超类限定而类型参数不行 + + T extends A + + ? extends A + ? super A + + + Class< T > 和 Class< ? > 区别 + 前面介绍了 ?和 T 的区别,那么对于,Class 又有什么区别呢? + + Class 在实例化的时候,T 要替换成具体类。Class 它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做申明: + + // 可以 + public Class clazz; + // 不可以,因为 T 需要指定类型 + public Class clazzT; \ No newline at end of file diff --git a/sources/killprocess_system_exit.md b/sources/killprocess_system_exit.md new file mode 100644 index 0000000..a18244f --- /dev/null +++ b/sources/killprocess_system_exit.md @@ -0,0 +1,12 @@ +### android.os.killprocess 和System.exit()区别 + +1. android.os.killprocess()可以杀掉任何第三方进程传入pid就可以,System.exit()只能杀掉本身 + + +2. System.exit它的意思是退出JVM(java虚拟机),在android中一样可以用,我们可以想像一下虚拟机都退出了当然执行System.exit的程序会完全退出,内存被释放。 + +在android手机中查看当前正在运行的进程时可以发现还可以查看"后台缓存的进程",你会发现很多退出了的程序还在后台缓存的进程中,如果不要让程序在后台缓存那么就可以用System.exit(0);来退出程序了,可以清除后台缓存的本进程。 + +System.exit(0),System.exit(1)的区别: + +参数0和1代表退出的状态,0表示正常退出,1表示异常退出(只要是非0的都为异常退出),即使不传0来执行也可以退出,该参数只是通知操作系统该程序是否是正常退出。 \ No newline at end of file diff --git a/sources/kotlin/builderModel.kt b/sources/kotlin/builderModel.kt new file mode 100644 index 0000000..ad0ba2e --- /dev/null +++ b/sources/kotlin/builderModel.kt @@ -0,0 +1,33 @@ + +class Car ( + val model: String?, + val year: Int +) { + private constructor(builder: Builder) : this(builder.model, builder.year) + + class Builder { + var model: String? = null + var year: Int = -1 + + fun build() = Car(this) + } + + companion object { + //核心 fun T.apply(f: T.() -> Unit): T { f(); return this } + //调用者本身扩展一个apply方法, apply 允许可以传递一个无参数的函数无返回值的函数,然后扩展到调用者身上,并在函数体调用这个函数,并放回自身 + // 为什么要Builder.() -> Unit扩展,因为这样里面就可以调用当前对象的其他方法,也就是能使用this + fun build(block: Builder.() -> Unit) = Builder().apply(block).build() + + + } +} +// usage 常规写法 +val car = Car.build({ + model = "aa" + year = 2018 +}) +// usage 简化写,好多初学者看到kotlin项目会一脸懵,这是什么意思,其实就是如果只有一个闭包参数就可以省略(),直接写{}, +val car = Car.build{ + model = "aa" + year = 2018 +} \ No newline at end of file diff --git a/sources/lifecycle.md b/sources/lifecycle.md new file mode 100644 index 0000000..44753a1 --- /dev/null +++ b/sources/lifecycle.md @@ -0,0 +1,226 @@ +#### Lifecycle 源码分析 + + + +#### Activity事件是如何分发的 + //SupportActivity 有个LifecycleRegistry 一个lifecycle登记处 + private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); + + public Lifecycle getLifecycle() { + return this.mLifecycleRegistry; + } + + //SupportActivity里面 + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ReportFragment.injectIfNeededIn(this); + } + + + public class ReportFragment extends Fragment { + private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle" + + ".LifecycleDispatcher.report_fragment_tag"; + + public static void injectIfNeededIn(Activity activity) { + // ProcessLifecycleOwner should always correctly work and some activities may not extend + // FragmentActivity from support lib, so we use framework fragments for activities + android.app.FragmentManager manager = activity.getFragmentManager(); + if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) { + manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit(); + // Hopefully, we are the first to make a transaction. + manager.executePendingTransactions(); + } + } + + static ReportFragment get(Activity activity) { + return (ReportFragment) activity.getFragmentManager().findFragmentByTag( + REPORT_FRAGMENT_TAG); + } + + private ActivityInitializationListener mProcessListener; + + private void dispatchCreate(ActivityInitializationListener listener) { + if (listener != null) { + listener.onCreate(); + } + } + + private void dispatchStart(ActivityInitializationListener listener) { + if (listener != null) { + listener.onStart(); + } + } + + private void dispatchResume(ActivityInitializationListener listener) { + if (listener != null) { + listener.onResume(); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + dispatchCreate(mProcessListener); + dispatch(Lifecycle.Event.ON_CREATE); + } + + @Override + public void onStart() { + super.onStart(); + dispatchStart(mProcessListener); + dispatch(Lifecycle.Event.ON_START); + } + + @Override + public void onResume() { + super.onResume(); + dispatchResume(mProcessListener); + dispatch(Lifecycle.Event.ON_RESUME); + } + + @Override + public void onPause() { + super.onPause(); + dispatch(Lifecycle.Event.ON_PAUSE); + } + + @Override + public void onStop() { + super.onStop(); + dispatch(Lifecycle.Event.ON_STOP); + } + + @Override + public void onDestroy() { + super.onDestroy(); + dispatch(Lifecycle.Event.ON_DESTROY); + // just want to be sure that we won't leak reference to an activity + mProcessListener = null; + } + + private void dispatch(Lifecycle.Event event) { + Activity activity = getActivity(); + if (activity instanceof LifecycleRegistryOwner) { + ((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event); + return; + } + + if (activity instanceof LifecycleOwner) { + Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle(); + if (lifecycle instanceof LifecycleRegistry) { + ((LifecycleRegistry) lifecycle).handleLifecycleEvent(event); + } + } + } + + void setProcessListener(ActivityInitializationListener processListener) { + mProcessListener = processListener; + } + + interface ActivityInitializationListener { + void onCreate(); + + void onStart(); + + void onResume(); + } + } + + + 利用ReportActivity,他是无视图的Fragment + + 无视图Fragment的技巧的优点: + + 他与创建他的Activity的生命周期一致(让这个Fragment具有生命周期感知能力),可以在不修改原有Activity生命周期代码的情况下,用Fragment来从外部插入方法。可拔插,解耦合,非常灵活。 + + + +- LifecycleRegistry + + // 早起版本Activity还没有实现LifecycleOwner + + public class MainActivity extends Activity implements LifecycleOwner { + private LifecycleRegistry mLifecycleRegistry; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mLifecycleRegistry = new LifecycleRegistry(this); + mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + mLifecycleRegistry.addObserver(new TestObserver()); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return mLifecycleRegistry; + } + + @Override + public void onStart() { + super.onStart(); + mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START); + } + + @Override + public void onResume() { + super.onResume(); + mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME); + } + + @Override + public void onPause() { + mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE); + super.onPause(); + } + + @Override + public void onStop() { + mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP); + super.onStop(); + } + + @Override + public void onDestroy() { + mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); + super.onDestroy(); + } + } + + + + public class LifecycleRegistry extends Lifecycle { + + public LifecycleRegistry(@NonNull LifecycleOwner provider) { + //保存了Activity弱引用,防止Activity被销毁其他地方还持有lifecycle + mLifecycleOwner = new WeakReference<>(provider); + mState = INITIALIZED; + } + + public void addObserver(@NonNull LifecycleObserver observer) { + + #### handleLifecycleEvent分发状态 + + + public void handleLifecycleEvent(@NonNull Lifecycle.Event event) { + State next = getStateAfter(event); + moveToState(next); + } + + private void moveToState(State next) { + if (mState == next) { + return; + } + mState = next; + if (mHandlingEvent || mAddingObserverCounter != 0) { + mNewEventOccurred = true; + // we will figure out what to do on upper level. + return; + } + mHandlingEvent = true; + //这个方法里面遍历观察者进行分发 + sync(); + mHandlingEvent = false; + } \ No newline at end of file diff --git a/sources/linkblockingqueue.md b/sources/linkblockingqueue.md new file mode 100644 index 0000000..ff0e8a0 --- /dev/null +++ b/sources/linkblockingqueue.md @@ -0,0 +1,71 @@ +### LinkBlockingQueue 原理 + +LinkBlockingQueue 内部使用了互斥锁ReentrantLock来实现,通过Condition来实现阻塞通知 + + +通过观察源码 + + + public E take() throws InterruptedException { + E x; + int c = -1; + final AtomicInteger count = this.count; + final ReentrantLock takeLock = this.takeLock; + takeLock.lockInterruptibly(); + try { + while (count.get() == 0) { + notEmpty.await(); + } + //添加数据 + x = dequeue(); + //原子化-1, + c = count.getAndDecrement(); + if (c > 1) + notEmpty.signal(); + } finally { + takeLock.unlock(); + } + // 如果上一次是capacity,证明put线程阻塞,所以要唤醒 + if (c == capacity) + signalNotFull(); + return x; + } + + + + public void put(E e) throws InterruptedException { + if (e == null) throw new NullPointerException(); + // Note: convention in all put/take/etc is to preset local var + // holding count negative to indicate failure unless set. + int c = -1; + Node node = new Node(e); + final ReentrantLock putLock = this.putLock; + final AtomicInteger count = this.count; + putLock.lockInterruptibly(); + try { + /* + * Note that count is used in wait guard even though it is + * not protected by lock. This works because count can + * only decrease at this point (all other puts are shut + * out by lock), and we (or some other waiting put) are + * signalled if it ever changes from capacity. Similarly + * for all other uses of count in other wait guards. + */ + while (count.get() == capacity) { + notFull.await(); + } + // 当我们条件通过时我们会添加新数据, + enqueue(node); + // 添加完后此时如何有数据被其他的线程取出,下面方法会保证count的一致性, + // 比如原本是1,被取出一个,这个时候就是0,再加1结果就是1 + c = count.getAndIncrement(); + // c 是返回原来的数量,所以+1是本次数量 + if (c + 1 < capacity) + notFull.signal(); + } finally { + putLock.unlock(); + } + // 如果上一次是0,证明take线程阻塞,所以要唤醒 + if (c == 0) + signalNotEmpty(); + } \ No newline at end of file diff --git a/sources/livedata.md b/sources/livedata.md new file mode 100644 index 0000000..3464d8e --- /dev/null +++ b/sources/livedata.md @@ -0,0 +1,62 @@ +### LiveData使用介绍 + +- LiveData 可以观察数据变化的框架 +- LiveData 内部维护一个Observers列表 + + private SafeIterableMap, LifecycleBoundObserver> mObservers = + new SafeIterableMap<>(); + +- 如果一个Observer的生命周期处于STARTED或RESUMED状态,那么LiveData将认为这个Observer处于活跃状态.LiveData仅通知活跃的Observer去更新UI。非活跃状态的Observer,即使订阅了LiveData,也不会收到更新的通知。 + +- observe 观察数据会注入一个LifecycleOwner,和一个observer,同时使用装饰者模式,将二者封装到一个新的Observer,添加到Observers列表中 + 然后使用lifecycle 添加这个wrapper用来监测生命周期,当有新的数据的时候,会遍历列表,先判断owner的状态是否可用,然后进行分发 + + + public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) { + if (owner.getLifecycle().getCurrentState() == DESTROYED) { + // ignore + return; + } + LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); + LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper); + if (existing != null && existing.owner != wrapper.owner) { + throw new IllegalArgumentException("Cannot add the same observer" + + " with different lifecycles"); + } + if (existing != null) { + return; + } + owner.getLifecycle().addObserver(wrapper); + } + +- LiveData内部没有任何线程安全控制,那么线程安全吗? 答案当然是安全的 + 如果event 发时所在的线程是主线程,并且是onStart OnResume onPause状态时,数据就会被执行,而不会考虑再次过程中关闭页面,导致onStop,onDestroy,因为,我们知道UI线程是一个一个回调的,比如想想一下onResume发出一个耗时事件,0.01秒,点击返回键使之结束;实际情况也会先执行耗时任务,执行完后再执行结束函数,最后再回调onStop onDestory等方法 + +#### LiveData的优点 +在项目中使用LiveData,会有以下优点: + +确保UI符合数据状态 +LiveData遵循观察者模式。当生命周期状态改变时,LiveData会向Observer发出通知。您可以把更新UI的代码合并在这些Observer对象中。不必去考虑导致数据变化的各个时机, -次数据有变化,观察者都会去更新UI。 +没有内存泄漏 +观察会绑定具有生命周期的对象,并在这个绑定的对象被销毁后自行清理。 +不会因停止活动发生而崩溃 +如果观察的生命周期处于非活跃状态,例如在后退堆栈中的活动,就不会收到任何LiveData事件的通知。 +不需要手动处理生命周期 +UI组件只需要去观察相关数据,不需要手动去停止或恢复观察.LiveData会进行自动管理这些事情,因为在观察时,它会感知到相应组件的生命周期变化。 +始终保持最新的数据 +如果一个对象的生命周期变到非活跃状态,它将在再次变为活跃状态时接收最新的数据。例如,后台活动在返回到前台后立即收到最新数据。 +应对正确配置更改 +如果一个活动或片段由于配置更改(如设备旋转)而重新创建,它会立即收到最新的可用数据。 +共享资源 +您可以使用单例模式扩展LiveData对象并包装成系统服务,以便在应用程序中进行共享.LiveData对象一旦连接到系统服务,任何需要该资源的Observer都只需观察这个LiveData对象。有关更多信息,请参阅扩展LiveData。 +使用LiveData对象 + +#### 按照以下步骤使用LiveData对象: + +创建一个LiveData的实例来保存特定类型的数据。这通常在ViewModel类中完成。 +创建一个定义了onChanged()方法的Observer对象,当LiveData对象保存的数据发生变化时,onChanged()方法可以进行相应的处理。您通常在UI控制器(如Activity或Fragment)中创建Observer对象。 +使用observe()方法将Observer对象注册到LiveData对象。observe()方法还需要一个LifecycleOwner对象作为参数.Observer对象订阅了LiveData对象,便会在数据发生变化时发出通知。您通常需要UI控制器(如活动或片段)中注册观测对象。 +注意:您可以使用observeForever(Observer)方法注册一个没有关联LifecycleOwner对象的Observer。在这种情况下,Observer被认为始终处于活动状态,因此当有数据变化时总是会被通知。您可以调用removeObserver(观察员)方法移除这些观察员。 + +  当你更新LiveData对象中存储的数据时,所有注册了的Observer,只要所绑定的LifecycleOwner处于活动状态,就会被触发通知 +  .LiveData允许UI控制器Observer订阅更新。当LiveData对象所保存的数据发生变化时,UI会在响应中自动更新 diff --git a/sources/locktype.md b/sources/locktype.md new file mode 100644 index 0000000..a9e6720 --- /dev/null +++ b/sources/locktype.md @@ -0,0 +1,165 @@ +#### 锁类型 + + +1. 我们常说的并发控制,一般都和数据库管理系统(DBMS)有关,在DBMS中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。 + + 实现并发控制的主要手段大致可以分为 乐观并发控制 和 悲观并发控制 两种。 + +2. 无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念 + +3. 悲观锁 之所以叫做悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。 + + 但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。 + +4. 乐观锁( Optimistic Locking ) 是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。 + + 相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。 + + 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。 + + CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 + + 在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。 + + +#### 高并发环境下锁粒度把控是一门重要的学问,选择一个好的锁,在保证数据安全的情况下,可以大大提升吞吐率,进而提升性能。 + + + +CAS算法 + +即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数 + +需要读写的内存值 V +进行比较的值 A +拟写入的新值 B +当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。 + +乐观锁的缺点 +ABA 问题是乐观锁一个常见的问题 + +1 ABA 问题 +如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。 + +JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 +--------------------- + +1. ABA 问题 +如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。 + +JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 + +2. 循环时间长开销大 +自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。 + +3. 只能保证一个共享变量的原子操作 +CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。 + + + +CAS与synchronized的使用情景 +简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多) + +对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。 +对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。 +补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS +--------------------- + + + +#### 如何选择 + +在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。 + +1. 乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。 + +2. 悲观锁依赖数据库锁,效率低。更新失败的概率比较低。 + +**随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被使用到生产环境中了,尤其是并发量比较大的业务场景** + + +### Java 并发编程不可不知的七种锁类型与注意事项 + + + +锁是解决并发冲突的重要工具。在开发中我们会用到很多类型的锁,每种锁都有其自身的特点和适用范围。需要深刻理解锁的理念和区别,才能正确、合理地使用锁。 + +常用锁类型 +乐观锁与悲观锁 +悲观锁对并发冲突持悲观态度,先取锁后访问数据,能够较大程度确保数据安全性。而乐观锁认为数据冲突的概率比较低,可以尽可能多地访问数据,只有在最终提交数据进行持久化时才获取锁。 + +悲观锁总是先获取锁,会增加很多额外的开销,也增加了死锁的几率。尤其是对于读操作,不会修改数据,使用悲观锁大大增加系统的响应时间。乐观锁最后一步才提交数据,死锁的几率比较低,但是如果有多个事务同时处理相同数据也有几率会冲突甚至导致系统异常。 + +传统关系型数据库常常使用悲观锁,以提高数据安全性。使用乐观锁的场景,通常用版本号来确保数据安全。 + +- 自旋锁 + + 自旋锁会让处于等待状态的线程执行空循环一段时间,执行完空循环后如果能够获取锁就立即获取锁,否则才挂起线程。使用自旋锁,能够降低等待线程被挂起的概率。线程进入阻塞状态再次唤醒,需要在用户态和内核态之间进行切换,自旋锁避免了进入内核态,因此有比较好的性能。 + + 自旋锁适用于竞争不激烈且线程任务执行时间短的场景。但是对于竞争激烈或者任务执行时间长的场景,不适合使用自旋锁,否则会浪费 CPU 时间片。 + +- 重入锁 + + Java 中提供的可重入锁 ReentrantLock,是一种递归无阻塞的同步机制,可以在外层方法已经加锁的情况下,让内层方法再次获取锁。ReentrantLock 维护了一个计数器,每加锁一次计数器加一,解锁一次计数器减一。Java 中的 synchronized 也是一种可重入锁。 + +- 轮询锁与定时锁 + + 轮询锁是通过线程不断尝试获取锁来实现的,可以避免发生死锁,可以更好地处理错误场景。Java 中可以通过调用锁的 tryLock 方法来进行轮询。tryLock 方法还提供了一种支持定时的实现,可以通过参数指定获取锁的等待时间。如果可以立即获取锁那就立即返回,否则等待一段时间后返回。 + +- 读写锁 + + 读写锁 ReadWriteLock 可以优雅地实现对资源的访问控制,具体实现为 ReentrantReadWriteLock。读写锁提供了读锁和写锁两把锁,在读数据时使用读锁,在写数据时使用写锁。 + + 读写锁允许有多个读操作同时进行,但只允许有一个写操作执行。如果写锁没有加锁,则读锁不会阻塞,否则需要等待写入完成。 + + ReadWriteLock lock = new ReentrantReadWriteLock(); + Lock readLock = lock.readLock(); + Lock writeLock = lock.writeLock(); +#### 锁的使用 + +减小锁的范围 +加锁后可以确保一个方法或一段代码只有一个线程访问,因此锁定范围要尽可能小。比如使用 synchronized 时,能对代码块进行加锁,就尽量不要对方法进行加锁。 + +对象锁与类锁 +能锁对象,就不要锁定类,尽量控制范围。锁定类以后,所有的线程使用同一把锁,同一时刻只有一个线程可以加锁;而锁定对象,可以增加锁的数量,提高并发的效率。 + +锁的公平性 +大部分锁都支持设置公平性:公平锁是指按照线程等待的时间来决定哪个线程先获取锁,非公平锁是指随机选择一个线程来获取锁。重入锁和读写锁默认都是非公平锁,也可以通过参数来设置。使用时需要根据具体场景来决定设置公平或非公平。 + +锁消除 +如无必要,不要使用锁。Java 虚拟机也可以根据逃逸分析判断出加锁的代码是否线程安全,如果确认线程安全虚拟机会进行锁消除提高效率。 + +锁粗化 +如果一段代码需要使用多个锁,建议使用一把范围更大的锁来提高执行效率。Java 虚拟机也会进行优化,如果发现同一个对象锁有一系列的加锁解锁操作,虚拟机会进行锁粗化来降低锁的耗时。 + + + + +- 什么是互斥锁? + + 在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。 + 如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。 + +- 什么是共享锁? + + 互斥锁要求只能有一个线程访问被保护的资源,共享锁从字面来看也即是允许多个线程共同访问资源。 + +- 什么是读写锁? + + 读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。 + 读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态 + +- 可重入锁 + + 可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。说的更高深一点可重入锁是一种递归无阻塞的同步机制。对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。 + +- 独享锁/共享锁 + + 独享锁是指该锁一次只能被一个线程所持有。共享锁是指该锁可被多个线程所持有。对于Java ReentrantLock(互斥锁)而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock(读写锁),其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。对于Synchronized而言,当然是独享锁。 + + +- Java 中概念 + +-- 同步锁 在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。一般使用 使用synchronized关键字来实现 + +-- 互斥锁 ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。 \ No newline at end of file diff --git a/sources/lru.md b/sources/lru.md new file mode 100644 index 0000000..3682661 --- /dev/null +++ b/sources/lru.md @@ -0,0 +1,425 @@ +### LRU 算法 + +#### 介绍 +1. LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。 +#### 原理 +2. 最常见的实现是使用一个链表保存缓存数据 + 1. 新数据插入到链表头部; + 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部; + 3. 当链表满的时候,将链表尾部的数据丢弃。 +#### 分析 +【命中率】 +当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。 + +命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。 + +#### 实现3这种方式 + +1. 使用LinkHashMap inheritance(继承)方式 + + *采用inheritance方式实现比较简单,而且实现了Map接口,在多线程环境使用时可以使用 Collections.synchronizedMap()方法实现线程安全操作,当然也可以为每个方法都手动加锁,下面就是手动为每个加锁实现方式* + + + import java.util.ArrayList; + import java.util.Collection; + import java.util.LinkedHashMap; + import java.util.concurrent.locks.Lock; + import java.util.concurrent.locks.ReentrantLock; + import java.util.Map; + + + /** + * 类说明:利用LinkedHashMap实现简单的缓存, 必须实现removeEldestEntry方法,具体参见JDK文档 + * + * @author qiyue + * + * @param + * @param + */ + public class LRULinkedHashMap extends LinkedHashMap { + private final int maxCapacity; + + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + private final Lock lock = new ReentrantLock(); + + public LRULinkedHashMap(int maxCapacity) { + super(maxCapacity, DEFAULT_LOAD_FACTOR, true); + this.maxCapacity = maxCapacity; + } + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry eldest) { + return size() > maxCapacity; + } + @Override + public boolean containsKey(Object key) { + try { + lock.lock(); + return super.containsKey(key); + } finally { + lock.unlock(); + } + } + + + @Override + public V get(Object key) { + try { + lock.lock(); + return super.get(key); + } finally { + lock.unlock(); + } + } + + @Override + public V put(K key, V value) { + try { + lock.lock(); + return super.put(key, value); + } finally { + lock.unlock(); + } + } + + public int size() { + try { + lock.lock(); + return super.size(); + } finally { + lock.unlock(); + } + } + + public void clear() { + try { + lock.lock(); + super.clear(); + } finally { + lock.unlock(); + } + } + + public Collection> getAll() { + try { + lock.lock(); + return new ArrayList>(super.entrySet()); + } finally { + lock.unlock(); + } + } + } + +2. 使用LinkHashMap delegation(委托)方式 + + *delegation方式实现更加优雅一些,但是由于没有实现Map接口,所以线程同步就需要自己搞定了* + + + + import java.util.LinkedHashMap; + import java.util.Map; + import java.util.Set; + + /** + * Created by qiyue on 18-5-13. + */ + public class LRUCache { + + private final int MAX_CACHE_SIZE; + private final float DEFAULT_LOAD_FACTOR = 0.75f; + LinkedHashMap map; + + public LRUCache(int cacheSize) { + MAX_CACHE_SIZE = cacheSize; + //根据cacheSize和加载因子计算hashmap的capactiy,+1确保当达到cacheSize上限时不会触发hashmap的扩容, + int capacity = (int) Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTOR) + 1; + map = new LinkedHashMap(capacity, DEFAULT_LOAD_FACTOR, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX_CACHE_SIZE; + } + }; + } + + public synchronized void put(K key, V value) { + map.put(key, value); + } + + public synchronized V get(K key) { + return map.get(key); + } + + public synchronized void remove(K key) { + map.remove(key); + } + + public synchronized Set> getAll() { + return map.entrySet(); + } + + public synchronized int size() { + return map.size(); + } + + public synchronized void clear() { + map.clear(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue())); + } + return sb.toString(); + } + } + +3. 一种是自己设计数据结构,使用链表+HashMap方式 + + 1. 自定义数据结构实现1 + + + import java.util.HashMap; + + /** + * Created by qiyue on 18-5-12. + */ + public class LRUCache { + + private final int MAX_CACHE_SIZE; + private Entry first; + private Entry last; + + private HashMap> hashMap; + + public LRUCache(int cacheSize) { + MAX_CACHE_SIZE = cacheSize; + hashMap = new HashMap>(); + } + + public synchronized void put(K key, V value) { + Entry entry = getEntry(key); + if (entry == null) { + if (hashMap.size() >= MAX_CACHE_SIZE) { + hashMap.remove(last.key); + removeLast(); + } + entry = new Entry(); + entry.key = key; + } + entry.value = value; + moveToFirst(entry); + hashMap.put(key, entry); + } + + public synchronized V get(K key) { + Entry entry = getEntry(key); + if (entry == null) return null; + moveToFirst(entry); + return entry.value; + } + + public synchronized void remove(K key) { + Entry entry = getEntry(key); + if (entry != null) { + if (entry.pre != null) entry.pre.next = entry.next; + if (entry.next != null) entry.next.pre = entry.pre; + if (entry == first) first = entry.next; + if (entry == last) last = entry.pre; + } + hashMap.remove(key); + } + + private synchronized void moveToFirst(Entry entry) { + if (entry == first) return; + if (entry.pre != null) entry.pre.next = entry.next; + if (entry.next != null) entry.next.pre = entry.pre; + if (entry == last) last = last.pre; + + if (first == null || last == null) { + first = last = entry; + return; + } + + entry.next = first; + first.pre = entry; + first = entry; + entry.pre = null; + } + + private synchronized void removeLast() { + if (last != null) { + last = last.pre; + if (last == null) first = null; + else last.next = null; + } + } + + + private synchronized Entry getEntry(K key) { + return hashMap.get(key); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + Entry entry = first; + while (entry != null) { + sb.append(String.format("%s:%s ", entry.key, entry.value)); + entry = entry.next; + } + return sb.toString(); + } + + class Entry { + public Entry pre; + public Entry next; + public K key; + public V value; + } + } + + + 2. 自定义数据结构实现2::注意下面是非线程安全的使用时需要加锁 + + + public class LRUCache { + /** + * 链表节点 + * @author Administrator + * + */ + class CacheNode { + CacheNode prev;//前一节点 + CacheNode next;//后一节点 + Object value;//值 + Object key;//键 + CacheNode() { + } + } + + public LRUCache(int i) { + currentSize = 0; + cacheSize = i; + nodes = new Hashtable(i);//缓存容器 + } + + /** + * 获取缓存中对象 + * @param key + * @return + */ + public Object get(Object key) { + CacheNode node = (CacheNode) nodes.get(key); + if (node != null) { + moveToHead(node); + return node.value; + } else { + return null; + } + } + + /** + * 添加缓存 + * @param key + * @param value + */ + public void put(Object key, Object value) { + CacheNode node = (CacheNode) nodes.get(key); + + if (node == null) { + //缓存容器是否已经超过大小. + if (currentSize >= cacheSize) { + if (last != null)//将最少使用的删除 + nodes.remove(last.key); + removeLast(); + } else { + currentSize++; + } + + node = new CacheNode(); + } + node.value = value; + node.key = key; + //将最新使用的节点放到链表头,表示最新使用的. + moveToHead(node); + nodes.put(key, node); + } + + /** + * 将缓存删除 + * @param key + * @return + */ + public Object remove(Object key) { + CacheNode node = (CacheNode) nodes.get(key); + if (node != null) { + if (node.prev != null) { + node.prev.next = node.next; + } + if (node.next != null) { + node.next.prev = node.prev; + } + if (last == node) + last = node.prev; + if (first == node) + first = node.next; + } + return node; + } + + public void clear() { + first = null; + last = null; + } + + /** + * 删除链表尾部节点 + * 表示 删除最少使用的缓存对象 + */ + private void removeLast() { + //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象) + if (last != null) { + if (last.prev != null) + last.prev.next = null; + else + first = null; + last = last.prev; + } + } + + /** + * 移动到链表头,表示这个节点是最新使用过的 + * @param node + */ + private void moveToHead(CacheNode node) { + if (node == first) + return; + if (node.prev != null) + node.prev.next = node.next; + if (node.next != null) + node.next.prev = node.prev; + if (last == node) + last = node.prev; + if (first != null) { + node.next = first; + first.prev = node; + } + first = node; + node.prev = null; + if (last == null) + last = first; + } + private int cacheSize; + private Hashtable nodes;//缓存容器 + private int currentSize; + private CacheNode first;//链表头 + private CacheNode last;//链表尾 + } + +#### 以上3中方式都没有问题,看个人喜好 + + + \ No newline at end of file diff --git a/sources/matrix_table.md b/sources/matrix_table.md new file mode 100644 index 0000000..16ae175 --- /dev/null +++ b/sources/matrix_table.md @@ -0,0 +1,43 @@ +### 邻接矩阵和邻接表比较 + + +1. 邻接矩阵 + +图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。 +设图G有n个顶点,则邻接矩阵是一个n*n的方阵 + +无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。 +即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的 + +- 从这个矩阵中,很容易知道图中的信息。 + +(1)要判断任意两顶点是否有边无边就很容易了; +(2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和; +(3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点; + + +2. 邻接表 + + 一般是 数组结构,每个数组中 对应一个单链表结构;因此这种数组与链表相结合的存储方法称为邻接表 + + + 邻接表的处理方法是这样的: + + (1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。 + + (2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则 + + (3)顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。 + + (4)边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。 + 对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。 + + +3. 两者区别 + + 对于一个具有n个顶点e条边的无向图 + 它的邻接表表示有n个顶点表结点2e个边表结点 + 对于一个具有n个顶点e条边的有向图 + 它的邻接表表示有n个顶点表结点e个边表结点 + 如果图中边的数目远远小于n2称作稀疏图,这是用邻接表表示比用邻接矩阵表示节省空间; + 如果图中边的数目接近于n2,对于无向图接近于n*(n-1)称作稠密图,考虑到邻接表中要附加链域,采用邻接矩阵表示法为宜。 \ No newline at end of file diff --git a/sources/media_player.md b/sources/media_player.md new file mode 100644 index 0000000..8aba656 --- /dev/null +++ b/sources/media_player.md @@ -0,0 +1,33 @@ +### MediaPlayer生命周期 + +###### 如果使用时MediaPlayer的状态不正确则会引发IllegalStateException异常。 + +  +1. Idle 状态:当使用new()方法创建一个MediaPlayer对象或者调用了其reset()方法时,该MediaPlayer对象处于idle状态。这两种方法的一个重要差别就是:如果在这个状态下调用了getDuration()等方法(相当于调用时机不正确),通过reset()方法进入idle状态的话会触发OnErrorListener.onError(),并且MediaPlayer会进入Error状态;如果是新创建的MediaPlayer对象,则并不会触发onError(),也不会进入Error状态。 + + +2. Initialized 状态:这个状态比较简单,MediaPlayer调用setDataSource()方法就进入Initialized状态,表示此时要播放的文件已经设置好了。 + + +3. Preparing 状态:这个状态比较好理解,主要是和prepareAsync()配合,如果异步准备完成,会触发OnPreparedListener.onPrepared(),进而进入Prepared状态。 + +4. Prepared 状态:初始化完成之后还需要通过调用prepare()或prepareAsync()方法,这两个方法一个是同步的一个是异步的,只有进入Prepared状态,才表明MediaPlayer到目前为止都没有错误,可以进行文件播放。 + + +5. Started 状态:显然,MediaPlayer一旦准备好,就可以调用start()方法,这样MediaPlayer就处于Started状态,这表明MediaPlayer正在播放文件过程中。可以使用isPlaying()测试MediaPlayer是否处于了Started状态。如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态,类似的,如果在该状态下MediaPlayer调用了seekTo()或者start()方法均可以让MediaPlayer停留在Started状态。 + + +6. Paused 状态:Started状态下MediaPlayer调用pause()方法可以暂停MediaPlayer,从而进入Paused状态,MediaPlayer暂停后再次调用start()则可以继续MediaPlayer的播放,转到Started状态,暂停状态时可以调用seekTo()方法,这是不会改变状态的。 +  + +7. PlaybackCompleted状态:文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener的onCompletion()方法。此时可以调用start()方法重新从头播放文件,也可以stop()停止MediaPlayer,或者也可以seekTo()来重新定位播放位置。 + +  +8. Stop 状态:Started或者Paused状态下均可调用stop()停止MediaPlayer,而处于Stop状态的MediaPlayer要想重新播放,需要通过prepareAsync()和prepare()回到先前的Prepared状态重新开始才可以。 + + +9. Error状态:如果由于某种原因MediaPlayer出现了错误,会触发OnErrorListener.onError()事件,此时MediaPlayer即进入Error状态,及时捕捉并妥善处理这些错误是很重要的,可以帮助我们及时释放相关的软硬件资源,也可以改善用户体验。通过setOnErrorListener(android.media.MediaPlayer.OnErrorListener)可以设置该监听器。如果MediaPlayer进入了Error状态,可以通过调用reset()来恢复,使得MediaPlayer重新返回到Idle状态 + +10. End 状态:通过release()方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release()方法释放掉,以释放相关的软硬件组件资源,这其中有些资源是只有一份的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会在进入任何其他状态了。 + +  \ No newline at end of file diff --git a/sources/netsafe.md b/sources/netsafe.md new file mode 100644 index 0000000..3d5f7d6 --- /dev/null +++ b/sources/netsafe.md @@ -0,0 +1,307 @@ +### 加密方案发展 + +1. 一开始使用非对称加密可以达到安全,但是效率太低, + +2. 使用对称秘钥效率高,但如果写入客户端,被破解后就可以解密双方数据,因为服务端也是相同的秘钥 + +3. 服务端生成会话密钥,进行非对称加密下发,咋一看这个会话密钥是加密后在网络上传输的,但实际上里面存在一个问题:由于公钥是公开的,因此任何人都能用公钥解开并获取你的会话密钥。因此,这个方案也不够完善。 +我们再回顾一下这个方案,发现存在一个本质的问题:这个会话密钥是服务端单独生成的,那一定会导致一种结果:只要客户端能最终解密出来,那黑客也一定能解密出来。因为对于服务端而言,客户端和黑客都是密钥的接收方,两者是平等的,无法区别的。 + +4. 客户端主动生成密钥 + + 具体方式如下: + + 服务端明文下发公钥给客户端; + 客户端生成一个随机数作为会话密钥,利用公钥加密后发送给服务端; + 服务端收到后,通过私钥对密文进行解密并获取随机数。 + 可以看出这个过程里,黑客就算拦截了我们的加密数据,也会因为没有私钥而无法获取会话密钥。 + + 看起来,方案很完美了,但实际上里面仍然存在一个漏洞:如果最开始下发的明文公钥就是假的呢?这会导致的结果是:用户全程和一个中间黑客通信:信任黑客的公钥,然后自以为安全的进行数据通信,结果把所有个人信息都暴露给黑客了,最重要的是,服务端对此一无所知! + +5. 公钥的合法性校验 + 为了公钥的合法性校验,人们建立了专门颁发证书的机构:"证书中心"(Certificate authority,简称CA),它会利用自己的私钥,将我们的公钥和相关信息进行加密,生成一个数字证书。在客户端内部,会存在一个受信任的根证书颁发机构,所以客户端在每次拿到一个公钥后,就去查询这个公钥是否在这里受信任证书列表中,如果不在,则认为公钥无效,不再进行后续操作。如果存在,再继续进行下一步认证。 + + 而这也和Https的实现机制相近。 + + + + +### https 原理 + +前提 非常重要的事情—加密方案中的加密算法都是公开的,而密钥是保密的,没有密钥就不能解密,有了密钥就能解密 * + +三个角色 客户端 代理服务器 真实服务器 + +所谓拦截破解拦截,就是代理服务器可以看到客户端发给服务器的数据和服务端返回给客户端的数据,并可以篡改 + + +指定信任证书 https 验证流程,(还有不指定的,就是会默认用根证书验证,但不能保证唯一性) + +用我们的证书去申请 CA证书,一对CA公钥 和 CA私钥 + +CA公钥留在客户端 CA私钥留在服务端 + +建立链接握手过程中 -》 通过代理服务器 -》到达服务端 + +服务端返回-》代理服务器一个CA证书(用CA私钥加密过的,只能CA公钥去解,里面包含了我们自己的公钥),代理服务通过破解的App方式可以拿到证书(CA公钥去解密证书,拿到里面的公钥)通过公钥加密 自己生成对称秘钥反给服务器,服务器拿手里私钥去解密,可以达到通信(但这并无意,我们自己也不知道和服务器通信做什么)这里可以达到冒充客户端作用,应该没意义吧 + +代理服务器将CA证书给客户端,客户端通过使用本地CA证书公钥解密,验证通过,取出公钥,生成对称秘钥,然后加密返回代理服务器,代理服务器拿到之后没有私钥,不能解密取出对称秘钥,只能返回给服务器 + + + + +#### 对于浏览器 + +客户不留你们的证书,只有几个最大的CA跟证书 + +通过CA根证书验证你们的CA证书,再通过CA证书验证网站证书 + +通过根证书验证 报CertificateException 验证失败,就会弹出是否要信任此证书,同意可继续浏览 + + + +#### 对于手机 + +内置也有几个最大的CA跟证书 + +当我们App使用https时,通常会报CertificateException,表示该证书用手机根证书验证失败,因此我们解决办法可能是信任所有证书, + +在客户端中覆盖google默认的证书检查机制(X509TrustManager),并且在代码中无任何校验SSL证书有效性相关代码 + + public class MySSLSocketFactory extends SSLSocketFactory { +     SSLContext sslContext = SSLContext.getInstance("TLS"); +   +     public MySSLSocketFactory(KeyStore truststore) +         throws NoSuchAlgorithmException, KeyManagementException, +             KeyStoreException, UnrecoverableKeyException { +         super(truststore); +   +         TrustManager tm = new X509TrustManager() { +                 public void checkClientTrusted(X509Certificate[] chain, +                     String authType) throws CertificateException { +                 } +   +                  //客户端并未对SSL证书的有效性进行校验,并且使用了自定义方法的方式覆盖android自带的校验方法 +                 public void checkServerTrusted(X509Certificate[] chain, +                     String authType) throws CertificateException { +                 } +   +                 public X509Certificate[] getAcceptedIssuers() { +                     return null; +                 } +             }; +   +         sslContext.init(null, new TrustManager[] { tm }, null); +     } + } + +#### 但问题出来了: + +如果用户手机中安装了一个恶意证书,那么就可以通过中间人攻击的方式进行窃听用户通信以及修改request或者response中的数据。 +手机银行中间人攻击过程: + +1. 客户端在启动时,传输数据之前需要客户端与服务端之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。 +2. 中间人在此过程中将客户端请求服务器的握手信息拦截后,模拟客户端请求给服务器(将自己支持的一套加密规则发送给服务器),服务器会从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给客户端。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。 +3. 而此时中间人会拦截下服务端返回给客户端的证书信息,并替换成自己的证书信息。 +4. 客户端得到中间人的response后,会选择以中间人的证书进行加密数据传输。 +5. 中间人在得到客户端的请求数据后,以自己的证书进行解密。 +6. 在经过窃听或者是修改请求数据后,再模拟客户端加密请求数据传给服务端。就此完成整个中间人攻击的过程。 + + + +#### 防护办法: + 1. + 服务端使用CA机构颁发的证书,客户端默认用根证书验证就可以,(但只能验证是否是合法的CA证书,只要是CA证书都能通过验证,需要做访问域名限制,(还是说默认CA颁发的就不敢做坏事,做坏事可以查)) + + 2. 添加指定添加指定信任证书(这个证书可以是CA申请的(使用CA机构颁发证书的方式可行,但是如果与实际情况相结合来看的话,时间和成本太高,所以目前很少有用此办法来做。),也可以是自己自签名生成的)
 + 在客户端内部,会存在一个受信任的根证书颁发机构,所以客户端在每次拿到一个公钥后,就去查询这个公钥是否在这里受信任证书列表中,如果不在,则认为公钥无效,不再进行后续操作。如果存在,再继续进行下一步认证。 + + + + public void initSSL() throws CertificateException, IOException, KeyStoreException, + NoSuchAlgorithmException, KeyManagementException { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + InputStream in = getAssets().open("ca.crt"); + Certificate ca = cf.generateCertificate(in); + + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + keystore.load(null, null); + keystore.setCertificateEntry("ca", ca); + + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keystore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + URL url = new URL("https://certs.cac.washington.edu/CAtest/"); + // URL url = new URL("https://github.com"); + HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); + urlConnection.setSSLSocketFactory(context.getSocketFactory()); + InputStream input = urlConnection.getInputStream(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); + StringBuffer result = new StringBuffer(); + String line = ""; + while ((line = reader.readLine()) != null) { + result.append(line); + } + Log.e("TTTT", result.toString()); + } + + +- 由于手机银行服务器其实是固定的,所以证书也是固定的,可以使用“证书或公钥锁定”的办法来防护证书有效性未作验证的问题。这相当不用默认根证书去校验,而是自己去校验 + + + + public final class PubKeyManager implements X509TrustManager { +     private static String PUB_KEY = "30820122300d06092a864886f70d0101" + +         "0105000382010f003082010a0282010100b35ea8adaf4cb6db86068a836f3c85"+ +         "5a545b1f0cc8afb19e38213bac4d55c3f2f19df6dee82ead67f70a990131b6bc"+ +         "ac1a9116acc883862f00593199df19ce027c8eaaae8e3121f7f329219464e657"+ +         "2cbf66e8e229eac2992dd795c4f23df0fe72b6ceef457eba0b9029619e0395b8"+ +         "609851849dd6214589a2ceba4f7a7dcceb7ab2a6b60c27c69317bd7ab2135f50"+ +         "c6317e5dbfb9d1e55936e4109b7b911450c746fe0d5d07165b6b23ada7700b00"+ +         "33238c858ad179a82459c4718019c111b4ef7be53e5972e06ca68a112406da38"+ +         "cf60d2f4fda4d1cd52f1da9fd6104d91a34455cd7b328b02525320a35253147b"+ +         "e0b7a5bc860966dc84f10d723ce7eed5430203010001"; +   +      //锁定证书公钥在apk中 +     public void checkServerTrusted(X509Certificate[] chain, String authType) +         throws CertificateException { +         if (chain == null) { +             throw new IllegalArgumentException( +                 "checkServerTrusted: X509Certificate array is null"); +         } +   +         if (!(chain.length > 0)) { +             throw new IllegalArgumentException( +                 "checkServerTrusted: X509Certificate is empty"); +         } +   +         if (!((null != authType) && authType.equalsIgnoreCase("RSA"))) { +             throw new CertificateException( +                 "checkServerTrusted: AuthType is not RSA"); +         } +   +         // Perform customary SSL/TLS checks +         try { +             TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); +             tmf.init((KeyStore) null); +   +             for (TrustManager trustManager : tmf.getTrustManagers()) { +                 ((X509TrustManager) trustManager).checkServerTrusted(chain, +                     authType); +             } +         } catch (Exception e) { +             throw new CertificateException(e); +         } +   +         // Hack ahead: BigInteger and toString(). We know a DER encoded Public Key begins +         // with 0?30 (ASN.1 SEQUENCE and CONSTRUCTED), so there is no leading 0?00 to drop. +         RSAPublicKey pubkey = (RSAPublicKey) chain[0].getPublicKey(); +         String encoded = new BigInteger(1 /* positive */, pubkey.getEncoded()).toString(16); +   +         // Pin it! +         final boolean expected = PUB_KEY.equalsIgnoreCase(encoded); +   +         if (!expected) { +             throw new CertificateException( +                 "checkServerTrusted: Expected public key: " + PUB_KEY + +                 ", got public key:" + encoded); +         } +     } + } + + + + 3. 上面方法都可避免中间攻击人冒充服务端,因为中间攻击人不能串改服务端证书,只能用和服务端相同的证书,即使证书我们可以通过服务端下发拿到,但是没有私钥也解密不了信息 + + 4. 以上如果以上还是存在一个问题,就是中间攻击人可以冒充客户端,使用根证书公钥进行解密获得公钥等信息,验证服务器数据签名,然后加密自己对称秘钥发给服务器,达到伪装客户端目的 + + 5. 为了客户端不被冒充使用Https双向验证,在客户端也保留个证书,发给服务端验证,但是如果客户端被破解取得证书也会被冒充 + + + + public class SSLHelper { + private static final String KEY_STORE_TYPE_BKS = "bks"; + private static final String KEY_STORE_TYPE_P12 = "PKCS12"; + public static final String KEY_STORE_CLIENT_PATH = "server.pfx";//P12文件 + private static final String KEY_STORE_TRUST_PATH = "client.truststore";//truststore文件 + public static final String KEY_STORE_PASSWORD = "123456";//P12文件密码 + private static final String KEY_STORE_TRUST_PASSWORD = "123456";//truststore文件密码 + + public static SSLSocketFactory getSSLSocketFactory(Context context) { + SSLSocketFactory factory = null; + + try { + // 服务器端需要验证的客户端证书 + KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); + // 客户端信任的服务器端证书 + KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS); + + InputStream ksIn = context.getResources().getAssets() + .open(KEY_STORE_CLIENT_PATH); + InputStream tsIn = context.getResources().getAssets() + .open(KEY_STORE_TRUST_PATH); + try { + keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); + trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + ksIn.close(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + tsIn.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + //信任管理器 + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + //密钥管理器 + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509"); + keyManagerFactory.init(keyStore, KEY_STORE_PASSWORD.toCharArray()); + //初始化SSLContext + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), + trustManagerFactory.getTrustManagers(), null); + factory = sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (UnrecoverableKeyException e) { + e.printStackTrace(); + } + + return factory; + } + + } + + OkHttpClient配置 + OkHttpClient client = new OkHttpClient.Builder() + .sslSocketFactory(SSLHelper.getSSLSocketFactory(getApplication())) + + + + 总结:Https双向验证是最终方案,可以防止服务端被冒充,也减少了客户端被冒充的几率,客户端即使被冒充也影响不大,因为一般我们都会在服务端做个总校验, + + 一般 浏览器客户端被冒充,我们应该仔细观察域名 + + + - [关于Https安全性问题、双向验证防止中间人攻击问题](https://blog.csdn.net/u010731949/article/details/50538280) + - [Android Https双向验证](https://www.jianshu.com/p/eef28062a2ea) + - [Https单向认证和双向认证](https://blog.csdn.net/duanbokan/article/details/50847612) diff --git a/sources/okhttp.md b/sources/okhttp.md new file mode 100644 index 0000000..b199410 --- /dev/null +++ b/sources/okhttp.md @@ -0,0 +1,674 @@ +### OKHttp源码分析 + +#### 1. 使用 + + + OkHttpClient client = new OkHttpClient(); + + String run(String url) throws IOException { + Request request = new Request.Builder() + .url(url) + .build(); + + Response response = client.newCall(request).execute(); + return response.body().string(); + } + + public static final MediaType JSON + = MediaType.parse("application/json; charset=utf-8"); + + OkHttpClient client = new OkHttpClient(); + + String post(String url, String json) throws IOException { + RequestBody body = RequestBody.create(JSON, json); + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + Response response = client.newCall(request).execute(); + return response.body().string(); + } + +#### 2. 源码分析 + + ##### Request、Response、Call 基本概念 + + ##### 源码分析用到的设计模式有,单例模式、工厂模式、构造者模式、责任链模式 + + Request:每一个HTTP请求包含一个URL、一个方法(GET或POST或其他)、一些HTTP头。请求还可能包含一个特定内容类型的数据类的主体部分。 + + Response:响应是对请求的回复,包含状态码、HTTP头和主体部分。 + + Call:OkHttp使用Call抽象出一个满足请求的模型,尽管中间可能会有多个请求或响应。执行Call有两种方式,同步或异步 + + + 1. 第一步创建OKHttpClient + + + + + // OKhttpClient.java + public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { + + final Dispatcher dispatcher;//处理异步请求分发策略 + final @Nullable Proxy proxy; + final List protocols; + final List connectionSpecs;//连接机构 + final List interceptors;//应用拦截器 + final List networkInterceptors;//网络拦截器 + final EventListener.Factory eventListenerFactory; + final ProxySelector proxySelector; + final CookieJar cookieJar; + final @Nullable Cache cache;//缓存 + final @Nullable InternalCache internalCache; + final SocketFactory socketFactory; + final @Nullable SSLSocketFactory sslSocketFactory; + final @Nullable CertificateChainCleaner certificateChainCleaner; + final HostnameVerifier hostnameVerifier; + final CertificatePinner certificatePinner; + final Authenticator proxyAuthenticator; + final Authenticator authenticator; + final ConnectionPool connectionPool; + final Dns dns; + final boolean followSslRedirects; + final boolean followRedirects; + final boolean retryOnConnectionFailure; + final int connectTimeout; + final int readTimeout; + final int writeTimeout; + final int pingInterval; + + public OkHttpClient() { + this(new Builder()); + } + + OkHttpClient(Builder builder) { + this.dispatcher = builder.dispatcher; + this.proxy = builder.proxy; + this.protocols = builder.protocols; + this.connectionSpecs = builder.connectionSpecs; + this.interceptors = Util.immutableList(builder.interceptors); + this.networkInterceptors = Util.immutableList(builder.networkInterceptors); + this.eventListenerFactory = builder.eventListenerFactory; + this.proxySelector = builder.proxySelector; + this.cookieJar = builder.cookieJar; + this.cache = builder.cache; + this.internalCache = builder.internalCache; + this.socketFactory = builder.socketFactory; + + boolean isTLS = false; + for (ConnectionSpec spec : connectionSpecs) { + isTLS = isTLS || spec.isTls(); + } + + if (builder.sslSocketFactory != null || !isTLS) { + this.sslSocketFactory = builder.sslSocketFactory; + this.certificateChainCleaner = builder.certificateChainCleaner; + } else { + X509TrustManager trustManager = Util.platformTrustManager(); + this.sslSocketFactory = newSslSocketFactory(trustManager); + this.certificateChainCleaner = CertificateChainCleaner.get(trustManager); + } + + if (sslSocketFactory != null) { + Platform.get().configureSslSocketFactory(sslSocketFactory); + } + + this.hostnameVerifier = builder.hostnameVerifier; + this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner( + certificateChainCleaner); + this.proxyAuthenticator = builder.proxyAuthenticator; + this.authenticator = builder.authenticator; + this.connectionPool = builder.connectionPool; + this.dns = builder.dns; + this.followSslRedirects = builder.followSslRedirects; + this.followRedirects = builder.followRedirects; + this.retryOnConnectionFailure = builder.retryOnConnectionFailure; + this.connectTimeout = builder.connectTimeout; + this.readTimeout = builder.readTimeout; + this.writeTimeout = builder.writeTimeout; + this.pingInterval = builder.pingInterval; + + if (interceptors.contains(null)) { + throw new IllegalStateException("Null interceptor: " + interceptors); + } + if (networkInterceptors.contains(null)) { + throw new IllegalStateException("Null network interceptor: " + networkInterceptors); + } + } + .... + .... + } + + //Call.java + public interface Call extends Cloneable { + + Request request(); + + Response execute() throws IOException; + + void enqueue(Callback responseCallback); + + void cancel(); + + boolean isExecuted(); + + boolean isCanceled(); + + Call clone(); + + interface Factory { + Call newCall(Request request); + } + } + + //WebSocket.java + public interface WebSocket { + + Request request(); + + long queueSize(); + + boolean send(String text); + + boolean send(ByteString bytes); + + boolean close(int code, @Nullable String reason); + + void cancel(); + + interface Factory { + + WebSocket newWebSocket(Request request, WebSocketListener listener); + } + } + + + + 总结:通过Build创建一个OKHttpClient对象,并根据配置,初始化一些缓存策略,创建一个Dispatcher对象 + +2. 第二步发送请求调用 OkHttpClient newCall + + + + //OKHttpClient.java + public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { + @Override + public Call newCall(Request request) { + return new RealCall(this, request, false /* for web socket */); + } + + .... + .... + + } + + + 总结 创建一个RealCall对象,这个对象实现了Call接口,Call接口主要包含request、execute、enqueue +3. 第三部执行异步请求 + + + + + final class RealCall implements Call { + final OkHttpClient client; + final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor; + private EventListener eventListener; + + .... + .... + .... + + @Override public void enqueue(Callback responseCallback) { + synchronized (this) { + if (executed) throw new IllegalStateException("Already Executed"); + executed = true; + } + captureCallStackTrace(); + eventListener.callStart(this); + client.dispatcher().enqueue(new AsyncCall(responseCallback)); + } + + .... + .... + + } + + + + 总结:调用RealCall的enqueue方法,判断是否正在执行,如果否添加到dispatcher中一个AsyncCall + + +4. 第四部 添加到Dispatcher中 + + + + + // Dispatcher.java + synchronized void enqueue(AsyncCall call) { + if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { + runningAsyncCalls.add(call); + executorService().execute(call); + } else { + readyAsyncCalls.add(call); + } + } + + +总结:首先判断正在运行的异步请求数量是否小于最大值,并且同一主机数量小于最大主机数量值,然后添加到运行队列,再去用线程池去执行这个call, +很明显线程池只运行Runnable,所以看一下AsyncCall代码实际上也是一个Runnable + + +5. 第五部 AsyncCall + + + + //AsyncCall.java + final class AsyncCall extends NamedRunnable { + private final Callback responseCallback;//回调函数 + + AsyncCall(Callback responseCallback) { + super("OkHttp %s", redactedUrl()); + this.responseCallback = responseCallback; + } + + .... + .... + + @Override protected void execute() { + boolean signalledCallback = false; + try { + Response response = getResponseWithInterceptorChain(); + if (retryAndFollowUpInterceptor.isCanceled()) { + signalledCallback = true; + responseCallback.onFailure(RealCall.this, new IOException("Canceled")); + } else { + signalledCallback = true; + responseCallback.onResponse(RealCall.this, response); + } + } catch (IOException e) { + if (signalledCallback) { + // Do not signal the callback twice! + Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); + } else { + eventListener.callFailed(RealCall.this, e); + responseCallback.onFailure(RealCall.this, e); + } + } finally { + client.dispatcher().finished(this); + } + } + } + + //NamedRunnable.Java + public abstract class NamedRunnable implements Runnable { + protected final String name; + + public NamedRunnable(String format, Object... args) { + this.name = Util.format(format, args); + } + + @Override public final void run() { + String oldName = Thread.currentThread().getName(); + Thread.currentThread().setName(name); + try { + execute(); + } finally { + Thread.currentThread().setName(oldName); + } + } + + protected abstract void execute(); + } + + + 所以:实际执行了个Runnable的run方法,run方法执行了execute方法,在execute里面调用getResponseWithInterceptorChain获得响应值 + + + +6. 第六部getResponseWithInterceptorChain实现 + + + + //RealCall方法 + .... + .... + Response getResponseWithInterceptorChain() throws IOException { + // Build a full stack of interceptors. + List interceptors = new ArrayList<>(); + interceptors.addAll(client.interceptors()); + interceptors.add(retryAndFollowUpInterceptor); + interceptors.add(new BridgeInterceptor(client.cookieJar())); + interceptors.add(new CacheInterceptor(client.internalCache())); + interceptors.add(new ConnectInterceptor(client)); + if (!forWebSocket) { + interceptors.addAll(client.networkInterceptors()); + } + interceptors.add(new CallServerInterceptor(forWebSocket)); + + Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, + originalRequest, this, eventListener, client.connectTimeoutMillis(), + client.readTimeoutMillis(), client.writeTimeoutMillis()); + + return chain.proceed(originalRequest); + } + } + + + // RealInterceptorChain.java + + public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, + RealConnection connection) throws IOException { + if (index >= interceptors.size()) throw new AssertionError(); + + calls++; + + // If we already have a stream, confirm that the incoming request will use it. + if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { + throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + + " must retain the same host and port"); + } + + // If we already have a stream, confirm that this is the only call to chain.proceed(). + if (this.httpCodec != null && calls > 1) { + throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + + " must call proceed() exactly once"); + } + + // Call the next interceptor in the chain. + RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, + connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, + writeTimeout); + Interceptor interceptor = interceptors.get(index); + Response response = interceptor.intercept(next); + + // Confirm that the next interceptor made its required call to chain.proceed(). + if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { + throw new IllegalStateException("network interceptor " + interceptor + + " must call proceed() exactly once"); + } + + // Confirm that the intercepted response isn't null. + if (response == null) { + throw new NullPointerException("interceptor " + interceptor + " returned null"); + } + + if (response.body() == null) { + throw new IllegalStateException( + "interceptor " + interceptor + " returned a response with no body"); + } + + return response; + } + + +总结:getResponseWithInterceptorChain 方法内部拿到所有自定义拦截器和系统内置拦截器,然后 + + 创建一个RealInterceptorChain对象递归执行proceed方法,直到返回一个response + +7. 拦截器执行说明 + + + 1)在配置 OkHttpClient 时设置的 interceptors; + 2)负责失败重试以及重定向的 RetryAndFollowUpInterceptor; + 3)负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor; + 4)负责读取缓存直接返回、更新缓存的 CacheInterceptor; + 5)负责和服务器建立连接的 ConnectInterceptor; + 6)配置 OkHttpClient 时设置的 networkInterceptors; + 7)负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。 + + OkHttp的这种拦截器链采用的是责任链模式,这样的好处是将请求的发送和处理分开,并且可以动态添加中间的处理方实现对请求的处理、短路等操作 + + +总结最终是由CallServerInterceptor去发送给服务器 + +8. 看一下 CallServerInterceptor + + + + public final class CallServerInterceptor implements Interceptor { + private final boolean forWebSocket; + + public CallServerInterceptor(boolean forWebSocket) { + this.forWebSocket = forWebSocket; + } + + @Override public Response intercept(Chain chain) throws IOException { + RealInterceptorChain realChain = (RealInterceptorChain) chain; + HttpCodec httpCodec = realChain.httpStream(); + StreamAllocation streamAllocation = realChain.streamAllocation(); + RealConnection connection = (RealConnection) realChain.connection(); + Request request = realChain.request(); + + long sentRequestMillis = System.currentTimeMillis(); + + realChain.eventListener().requestHeadersStart(realChain.call()); + httpCodec.writeRequestHeaders(request); + realChain.eventListener().requestHeadersEnd(realChain.call(), request); + + Response.Builder responseBuilder = null; + if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { + // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 + // Continue" response before transmitting the request body. If we don't get that, return + // what we did get (such as a 4xx response) without ever transmitting the request body. + if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { + httpCodec.flushRequest(); + realChain.eventListener().responseHeadersStart(realChain.call()); + responseBuilder = httpCodec.readResponseHeaders(true); + } + + if (responseBuilder == null) { + // Write the request body if the "Expect: 100-continue" expectation was met. + realChain.eventListener().requestBodyStart(realChain.call()); + long contentLength = request.body().contentLength(); + CountingSink requestBodyOut = + new CountingSink(httpCodec.createRequestBody(request, contentLength)); + BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); + + request.body().writeTo(bufferedRequestBody); + bufferedRequestBody.close(); + realChain.eventListener() + .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); + } else if (!connection.isMultiplexed()) { + // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection + // from being reused. Otherwise we're still obligated to transmit the request body to + // leave the connection in a consistent state. + streamAllocation.noNewStreams(); + } + } + + httpCodec.finishRequest(); + + if (responseBuilder == null) { + realChain.eventListener().responseHeadersStart(realChain.call()); + responseBuilder = httpCodec.readResponseHeaders(false); + } + + Response response = responseBuilder + .request(request) + .handshake(streamAllocation.connection().handshake()) + .sentRequestAtMillis(sentRequestMillis) + .receivedResponseAtMillis(System.currentTimeMillis()) + .build(); + + int code = response.code(); + if (code == 100) { + // server sent a 100-continue even though we did not request one. + // try again to read the actual response + responseBuilder = httpCodec.readResponseHeaders(false); + + response = responseBuilder + .request(request) + .handshake(streamAllocation.connection().handshake()) + .sentRequestAtMillis(sentRequestMillis) + .receivedResponseAtMillis(System.currentTimeMillis()) + .build(); + + code = response.code(); + } + + realChain.eventListener() + .responseHeadersEnd(realChain.call(), response); + + if (forWebSocket && code == 101) { + // Connection is upgrading, but we need to ensure interceptors see a non-null response body. + response = response.newBuilder() + .body(Util.EMPTY_RESPONSE) + .build(); + } else { + response = response.newBuilder() + .body(httpCodec.openResponseBody(response)) + .build(); + } + + if ("close".equalsIgnoreCase(response.request().header("Connection")) + || "close".equalsIgnoreCase(response.header("Connection"))) { + streamAllocation.noNewStreams(); + } + + if ((code == 204 || code == 205) && response.body().contentLength() > 0) { + throw new ProtocolException( + "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); + } + + return response; + } + + + + //StreamAllocation.java + public HttpCodec newStream( + OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { + int connectTimeout = chain.connectTimeoutMillis(); + int readTimeout = chain.readTimeoutMillis(); + int writeTimeout = chain.writeTimeoutMillis(); + int pingIntervalMillis = client.pingIntervalMillis(); + boolean connectionRetryEnabled = client.retryOnConnectionFailure(); + + try { + RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, + writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); + HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); + + synchronized (connectionPool) { + codec = resultCodec; + return resultCodec; + } + } catch (IOException e) { + throw new RouteException(e); + } + } + +总结:通过StreamAllocation 查找从ConnectionPool连接池查找一个RealConnection 然后创建一个HttpCodec(http编解码器) + + +9. ConnectionPool + // 连接池用一个队列保存 + private final Deque connections = new ArrayDeque<>(); + + public ConnectionPool() { + this(5, 5, TimeUnit.MINUTES); + } +10. RealConnection + + + //建立连接 + private void connectSocket(int connectTimeout, int readTimeout, Call call, + EventListener eventListener) throws IOException { + .... + .... + try { + Okio 有自己的流类型,那就是 Source 和 Sink,它们和 InputStream 与 OutputStream 类似,前者为输入流,后者为输出流。 + source = Okio.buffer(Okio.source(rawSocket)); + sink = Okio.buffer(Okio.sink(rawSocket)); + } catch (NullPointerException npe) { + if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) { + throw new IOException(npe); + } + } + } + + 总结:连接池默认是维持5个连接数,也就是socket连接数,建立链接会返回一个source 和 sink, + Okio 有自己的流类型,那就是 Source 和 Sink,它们和 InputStream 与 OutputStream 类似,前者为输入流,后者为输出流。 + + + + +10. 最终通过 httpCodec.finishRequest();发送请求 通过OutputStream,而最后通过OutputStream读取网络请求 + +11.请求结束后Dispatcher调用finish移除队列 + +#### 总结: + + + 1. 通过OKHttpClient 创建了一个Dispatcher ; + 2. 通过调用OKHttpClient newCall创建了一个RealCall; + 3. RealCall调用enqueue传入一个callBack + 4. enqueue方法里面通过持有的OKHttpClient的dispatcher调用enqueue方法放入一个AsyncCall + 5. AsynCall实现了Runnable方法 + 6. AsynCall的Runnable run方法实现:调用getResponseWithInterceptorChain去执行请求 + 7. getResponseWithInterceptorChain 递归调用所有拦截器去执行,而到ConnectionInercepter建立链接,最后CallServerIntercepter才去发送请求 + 8. Dispatcher内部会创建一个线程池,调用enqueue先加入队列,再去执行这个Runanble + + +12. Dispatcher 进一步分析 + + + public final class Dispatcher { + /** 最大并发请求数为64 */ + private int maxRequests = 64; + /** 每个主机最大请求数为5 */ + private int maxRequestsPerHost = 5; + + /** 线程池 */ + private ExecutorService executorService; + + /** 准备执行的请求 */ + private final Deque readyAsyncCalls = new ArrayDeque<>(); + + /** 正在执行的异步请求,包含已经取消但未执行完的请求 */ + private final Deque runningAsyncCalls = new ArrayDeque<>(); + + /** 正在执行的同步请求,包含已经取消单未执行完的请求 */ + private final Deque runningSyncCalls = new ArrayDeque<>(); + + public synchronized ExecutorService executorService() { + if (executorService == null) { + executorService = new ThreadPoolExecutor( + //corePoolSize 最小并发线程数,如果是0的话,空闲一段时间后所有线程将全部被销毁 + 0, + //maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理 + Integer.MAX_VALUE, + //keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间 + 60, + //单位秒 + TimeUnit.SECONDS, + //工作队列,先进先出 + new SynchronousQueue(), + //单个线程的工厂 + Util.threadFactory("OkHttp Dispatcher", false)); + } + return executorService; + } + +总结:可以看出,在Okhttp中,构建了一个核心为[0, Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做”OkHttp Dispatcher”的线程工厂。 + +也就是说,在实际运行中,当收到10个并发请求时,线程池会创建十个线程,当工作完成后,线程池会在60s后相继关闭所有线程。 + + +Dispatcher线程池总结 + +1)调度线程池Disptcher实现了高并发,低阻塞的实现 +2)采用Deque作为缓存,先进先出的顺序执行 +3)任务在try/finally中调用了finished函数,控制任务队列的执行顺序,而不是采用锁,减少了编码复杂性提高性能 + + +13. 其他拦截器分析 + + + + + + + + \ No newline at end of file diff --git a/sources/onMeasure.md b/sources/onMeasure.md new file mode 100644 index 0000000..5f8108f --- /dev/null +++ b/sources/onMeasure.md @@ -0,0 +1,42 @@ +### onMeasure 多次调用问题 + +比如FlowLayout onMeasure调用四次 + +父视图使用unspecified dimensions来将它的每个子视图都测量一次来算出它们到底需要多大尺寸,而这些子视图没被限制的尺寸的和太大或太小,所以会用精确数值再次调用measure()(也就是说,如果子视图不满意它们获得的区域大小,那么父视图将会干涉并设置第二次测量规则)。其中measure()方法会调用onMeasure()方法。 +代码中,由于把每行剩余空间重新分配,会调用了requestLayout()方法,这个方法又会导致measure()和onLayout()方法的再次调用。 +最后你会发现 onMeasure()方法调用了 1次*2*2=4次 onLayout()方法调用了 1次*2 =2次 + + + + +2.关于onmeasure两次调用 + +但是一直很疑惑UNSPECIFIED什么情况发生,今天测了下,发现在使用weigt时会调用,因为此时定义的值为0dp,这不就是UNSPECIFIED嘛! + +第一次应该是通过xml的设置进行计算 + + UNSPECIFIED时第一轮的onmeasure会多调用一次 + + UNSPECIFIED 0--0(第一个值是传入的参数size,第二个值是测量的大小,这都是执行完super.onmeaure后打印出的) + EXACTLY 1440--1440 + +第二次是在ondraw执行前再调用一次,比如 + +tv.post(new Runnable() { + +@Override +public void run() { +MainActivity.this.tv.setText("dddd"); +System.out.println("-"+tv.getMeasuredWidth()); + +} +}); + +此时第二次的onmeasure就起到了作用 + + +3. 第一次启动的时候还会多绘制一次有可能是3次 + +由源代码可以看出,当newSurface为真时,performTraversals函数并不会调用performDraw函数,而是调用scheduleTraversals函数,从而再次调用一次performTraversals函数,从而再次进行一次测量,布局和绘制过程。 + 我们由断点调试可以轻易看到,第一次performTraversals时的newSurface为真,而第二次时是假。 + \ No newline at end of file diff --git a/sources/recyclerView_listview.md b/sources/recyclerView_listview.md new file mode 100644 index 0000000..068cef4 --- /dev/null +++ b/sources/recyclerView_listview.md @@ -0,0 +1,37 @@ +### RecyclerView 和 ListView区别 + +1. RecyclerView封装了ViewHolder,抽象了分割线,等函数,使自定义起来非常好用 + + +2. 缓存层级不同,RecyclerView有4层缓存,ListView只有两层 + + +RecyclerView中的CacheViews(屏幕外)获取缓存时,是通过匹配item的pos获取目标位置的缓存,这样做的好处是,当数据元不变的时候,无需重新bindview.而同样是离屏的缓存,在ListView从mScrapViews根据pos获取相应的缓存,但并没有直接使用,而是重新getView,这样就会导致重新bindView. + + +ListView中通过pos获取的是view,即pos->view;而RecyclerView中通过pos获取的是ViewHolder,即pos->(view,ViewHolder,flag); +从流程上可以看出,标志flag的作用是判断view是否需要重新bindView,这也是RecyclerView实现局部刷新的一个核心. + + +由上文可知,RecyclerView的缓存机制更加完善,但是还没有达到质变的程度,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView. + +以RecyclerView中的notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重绘,过程为 + + + + +相对于AbsListView的两个子类ListView以及GridView来讲,RecyclerView最大一个特性就是灵活,主要体现在以下两个方面 + +多样式:可以对数据的展示进行自有定制,可以是列表,网格甚至是瀑布流,除此之外你还可以自定义样式 +定向刷新:可以对指定的Item数据进行刷新 +刷新动画:RecyclerView支持对Item的刷新添加动画 +添加装饰:相对于ListView以及GridView的单一的分割线,RecyclerView可以自定义添加分割样式 +今天我们的重点在于缓存,所以重点研究定向刷新,除了对整体数据进行刷新之外,RecyclerView还提供了很多定向刷新的方法。 + +跟ListView的RecycleBin一样,Recycler也是RecyclerView设计的一个专门用于回收ViewHolder的类,其实RecyclerView的缓存机制是在ListView的缓存机制的基础上进一步的完善,所以在Recycler中能看到很多跟RecycleBin一样的设计思想,在缓存这个层面上,RecyclerView实际上并没有做出太大的创新,最大的创新来源于给每一个ViewHolder增加了一个UpdateOp,通过这个标志可以进行定向刷新指定的Item,并且通过Payload参数可以对Item进行局部刷新,我觉得这个是RecyclerView最厉害的地方,大大提高了刷新时候的性能,如果数据源需要经常变动,那么RecyclerView是你最好的选择,没有之一,下面看一下Recycler是如何进行缓存的。 + +Recycler的缓存做了很多优化,实际上采用了LFU算法,也就是最少使用策略, + + +定向刷新 +可以对指定的Item进行刷新,是RecyclerView的又一特点,RecyclerView在观察者模式中的Observer中新增了很多方法,用于定向刷新,这个在前面的观察者模式中已经提到过,下面从源码的角度分析一下原理,我们拿adapter.notifyItemChanged(0),最终会RecyclerViewDataObserver的方法 diff --git a/sources/reference.md b/sources/reference.md new file mode 100644 index 0000000..e8d5699 --- /dev/null +++ b/sources/reference.md @@ -0,0 +1,112 @@ +### 强、软、弱、虚、引用 + +一、java.lang.ref包中提供了几个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对象的活动。 +#### 强引用 +1. 关于强引用引用的场景 + + 直接new出来的对象 + String str = new String(“yc”); + +2. 强引用介绍 + + 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 + + 通过引用,可以对堆中的对象进行操作。在某个函数中,当创建了一个对象,该对象被分配在堆中,通过这个对象的引用才能对这个对象进行操作。 + +3. 强引用的特点 + + 强引用可以直接访问目标对象。 + 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。 + 强引用可能导致内存泄露。 + +#### 软引用 + +1. 关于SoftReference软引用 + + SoftReference:软引用–>当虚拟机内存不足时,将会回收它指向的对象;需要获取对象时,可以调用get方法。 + + 可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆的使用率临近阈值时,才会回收软引用的对象。 + +2. 软引用应用场景 + + 例如从网络上获取图片,然后将获取的图片显示的同时,通过软引用缓存起来。当下次再去网络上获取图片时,首先会检查要获取的图片缓存中是否存在,若存在,直接取出来,不需要再去网络上获取。 + + View view = findViewById(R.id.button); + Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); + Drawable drawable = new BitmapDrawable(bitmap); + SoftReference drawableSoftReference = new SoftReference(drawable); + if(drawableSoftReference != null) { + view.setBackground(drawableSoftReference.get()); + } + +3. 这样使用软引用好处,正常是用来处理图片这种占用内存大的情况 + + 通过软引用的get()方法,取得drawable对象实例的强引用,发现对象被未回收。在GC在内存充足的情况下,不会回收软引用对象。此时view的背景显示. + + 实际情况中,我们会获取很多图片.然后可能给很多个view展示, 这种情况下很容易内存吃紧导致oom,内存吃紧,系统开始会GC。这次GC后,drawables.get()不再返回Drawable对象,而是返回null,这时屏幕上背景图不显示,说明在系统内存紧张的情况下,软引用被回收。 + + 使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。 + +4. 注意避免软引用获取对象为null + + 在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃 + + +#### 弱引用WeakReference + + +弱引用–>随时可能会被垃圾回收器回收,不一定要等到虚拟机内存不足时才强制回收。要获取对象时,同样可以调用get方法。 + +1. 特点 + + 如果一个对象只具有弱引用,那么在垃圾回收器线程扫描的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 + 不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象,因此可以用于缓存 + 弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 + + ***** WeakReference:防止内存泄漏,要保证内存被虚拟机回收 + + +2. 应用 + 1. 解决Handler内存泄露 + 2. 实现缓存,Android 官方建议使用WeakReference 而不是softReference + +#### 下面Java和Android环境下SoftReference 和WeakReference使用 + + + public static void main(String[] args) throws InterruptedException { + + initsoftReference(); + initweakReference(); + + Thread.sleep(2000); + System.gc(); + + if (softReference.get() == null) { + System.out.println("SoftReference : " + "null"); + }else{ + System.out.println("SoftReference : " + softReference.get()); + } + + + if (weakReference.get() == null) { + System.out.println("WeakReference : " + "null"); + }else{ + System.out.println("WeakReference : " + weakReference.get()); + } + + } + + private static void initsoftReference() { + softReference = new SoftReference(value_soft); + value_soft = null; + } + + private static void initweakReference() { + weakReference = new WeakReference(value_weak); + value_weak = null; + } + +#### 从上面的情况,我们还让你容易可以观察Android环境下与纯Java环境下两者直接的输出结果不同! +在Android环境下WeakReference 与SoftReference 两者输出结果一样。其实对于手机系统存在多应用,又对于内存是比较敏感的,自然对于内存释放会更加严格。 +试想一下,如果众多对象使用 SoftReference引用,大部分都是这也是为什么google不建议SoftReference 的原因之一。 + diff --git a/sources/requestlayout_invalidate_postInvalidate.md b/sources/requestlayout_invalidate_postInvalidate.md new file mode 100644 index 0000000..f99d75f --- /dev/null +++ b/sources/requestlayout_invalidate_postInvalidate.md @@ -0,0 +1,16 @@ +### requestLayout invalidate postInvalidate 区别和联系 + + + +1. requestLayout +当我们动态移动一个View的位置,或者View的大小、形状发生了变化的时候,我们可以在view中调用这个方法, + 会重新measure layout + +2. invalidate + 该方法的调用会引起View树的重绘,常用于内部调用(比如 setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法。那么我们来分析一下它的实现。 + invalidate有多个重载方法,但最终都会调用invalidateInternal方法,在这个方法内部,进行了一系列的判断,判断View是否需要重绘,接着为该View设置标记位,然后把需要重绘的区域传递给父容器,即调用父容器的invalidateChild方法。 + + invalidate 回溯给父类,最终到ViewRootImpl 绘制 + +3. postInvalidate 可以在非UI想成调用 + diff --git a/sources/retrofit.md b/sources/retrofit.md new file mode 100644 index 0000000..cc1fb14 --- /dev/null +++ b/sources/retrofit.md @@ -0,0 +1,426 @@ +### Retrofit2.0 源码分析 + + + +##### retrofit 一共就是17个类,24个注解 +#### 使用步骤 +1. 第一步根据网络API定义一个接口 + + public interface UserApi { + @GET("/api/columns/{user} ") + Call getAuthor(@Path("user") String user) + } + + +2. 第二步创建一个Retroft对象,并设置域名地址 + + public static final String API_URL = "https://zhuanlan.zhihu.com"; + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(API_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + +3. 第三步再用这个retrofit对象创建一个UserApi对象: + + UserApi api = retrofit.create(UserApi.class); + +4. 第四步调用这个API + + Call call = api.getAuthor("ustory"); + //异步使用enqueue /[enk'ju:]/ + call.enqueue(new Callback() { + @Override + public void onResponse(Response author) { + System.out.println("name: " + author.getName()); + } + @Override + public void onFailure(Throwable t) { + } + }); + //同步使用 [ˈeksɪkju:t] + +#### Retrofit 原理分析 + +1. Retrofit 实例化 + + + + public static final class Builder { + private final Platform platform; + private @Nullable okhttp3.Call.Factory callFactory; + private HttpUrl baseUrl; + private final List converterFactories = new ArrayList<>(); + private final List adapterFactories = new ArrayList<>(); + private @Nullable Executor callbackExecutor; + private boolean validateEagerly; + + Builder(Platform platform) { + this.platform = platform; + // Add the built-in converter factory first. This prevents overriding its behavior but also + // ensures correct behavior when using converters that consume all types. + converterFactories.add(new BuiltInConverters()); + } + + public Builder() { + this(Platform.get()); + } + .... + .... + public Retrofit build() { + if (baseUrl == null) { + throw new IllegalStateException("Base URL required."); + } + + okhttp3.Call.Factory callFactory = this.callFactory; + if (callFactory == null) { + callFactory = new OkHttpClient(); + } + + Executor callbackExecutor = this.callbackExecutor; + if (callbackExecutor == null) { + callbackExecutor = platform.defaultCallbackExecutor(); + } + + // Make a defensive copy of the adapters and add the default Call adapter. + List adapterFactories = new ArrayList<>(this.adapterFactories); + adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); + + // Make a defensive copy of the converters. + List converterFactories = new ArrayList<>(this.converterFactories); + + return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, + callbackExecutor, validateEagerly); + } + + + // 封装handler + static class MainThreadExecutor implements Executor { + private final Handler handler = new Handler(Looper.getMainLooper()); + + @Override public void execute(Runnable r) { + handler.post(r); + } + } + + //Platform.java + CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) { + if (callbackExecutor != null) { + return new ExecutorCallAdapterFactory(callbackExecutor); + } + return DefaultCallAdapterFactory.INSTANCE; + } + + + 总结:采用构造模式,通过Builder来实例化,包含有Platform用来获取平台信息, + callFactory是用来生成http请求对象的工厂,HttpUrl是对url的参数处理,List converterFactories用来存储数据转化工厂(比如返回json数据和转化成对象,发送的数据如何由对象转化为json),通过这个工厂用户和自己实现解析部分 + ,ListadapterFactories 适配Call类型,默认用的ExecutorCallAdapterFactory类型返回一个Call去执行请求,也可以通过配置RxJava2CallAdapterFactory()返回一个 Observable 去执行 + Executor callbackExecutor 回调调度器(通过平台进行判断,如果是Android平台使用的是MainThreadExecutor其实就是封装的handler);然后配置完以上参数就调用build,初始化callFactory为OKHttpClient,初始化callbackExecutor为MainExecutor + + +2. retrofit.create实例化Api接口对象 + + + // Retrofit.java + private final Map> serviceMethodCache = new ConcurrentHashMap<>(); + + public T create(final Class service) { + Utils.validateServiceInterface(service); + if (validateEagerly) { + eagerlyValidateMethods(service); + } + return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service }, + new InvocationHandler() { + private final Platform platform = Platform.get(); + + @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) + throws Throwable { + // If the method is a method from Object then defer to normal invocation. + if (method.getDeclaringClass() == Object.class) { + return method.invoke(this, args); + } + if (platform.isDefaultMethod(method)) { + return platform.invokeDefaultMethod(method, service, proxy, args); + } + ServiceMethod serviceMethod = + (ServiceMethod) loadServiceMethod(method); + OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); + return serviceMethod.callAdapter.adapt(okHttpCall); + } + }); + } + + + ServiceMethod loadServiceMethod(Method method) { + ServiceMethod result = serviceMethodCache.get(method); + if (result != null) return result; + + synchronized (serviceMethodCache) { + result = serviceMethodCache.get(method); + if (result == null) { + result = new ServiceMethod.Builder<>(this, method).build(); + serviceMethodCache.put(method, result); + } + } + return result; + } + + + + + + 总结:通过传过来的接口类型参数,使用Proxy.newProxyInstance穿件一个动态代理对象并返回, + 而所有的方法都将走invoke方法;方法内部先判断此方法如果是Object类的方法就直接调用,这个设计很精妙;然判断方法是否是平台方法,Android平台默认是false, + 然后loadServiceMethod,内部先查找缓存(private final Map> serviceMethodCache),没有就创建一个并加入缓存,并且创建一个OkHttpCall对象,用交给默认的CallAdapterFactory + 返回一个ExecutorCallbackCall对象,而ExecutorCallbackCall代理了okHttpCall对象,所以通过ExecutorCallbackCall调用enqueue和execute来调用OKHttpClient的Call对象enqueue和execute + + + +3. ServiceMethod + + + //ServiceMethod.java + static final class Builder { + .... + .... + + Builder(Retrofit retrofit, Method method) { + this.retrofit = retrofit; + this.method = method; + this.methodAnnotations = method.getAnnotations(); + this.parameterTypes = method.getGenericParameterTypes(); + this.parameterAnnotationsArray = method.getParameterAnnotations(); + } + + public ServiceMethod build() { + callAdapter = createCallAdapter(); + responseType = callAdapter.responseType(); + if (responseType == Response.class || responseType == okhttp3.Response.class) { + throw methodError("'" + + Utils.getRawType(responseType).getName() + + "' is not a valid response body type. Did you mean ResponseBody?"); + } + responseConverter = createResponseConverter(); + + for (Annotation annotation : methodAnnotations) { + parseMethodAnnotation(annotation); + } + + if (httpMethod == null) { + throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)."); + } + + if (!hasBody) { + if (isMultipart) { + throw methodError( + "Multipart can only be specified on HTTP methods with request body (e.g., @POST)."); + } + if (isFormEncoded) { + throw methodError("FormUrlEncoded can only be specified on HTTP methods with " + + "request body (e.g., @POST)."); + } + } + + int parameterCount = parameterAnnotationsArray.length; + parameterHandlers = new ParameterHandler[parameterCount]; + for (int p = 0; p < parameterCount; p++) { + Type parameterType = parameterTypes[p]; + if (Utils.hasUnresolvableType(parameterType)) { + throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", + parameterType); + } + + Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; + if (parameterAnnotations == null) { + throw parameterError(p, "No Retrofit annotation found."); + } + + parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); + } + + .... + .... + + return new ServiceMethod<>(this); + } + + 总结:ServiceMethod封装了调用方法,内部通过Build去构造自己,Build的构造法方法内部获取 注解信息和参数信息,在build时解析配置的注解信息; + + +4. OkHttpCall + + + + final class OkHttpCall implements Call { + private final ServiceMethod serviceMethod; + @GuardedBy("this") + private @Nullable okhttp3.Call rawCall; + + .... + .... + @Override + public synchronized Request request() { + okhttp3.Call call = rawCall; + if (call != null) { + return call.request(); + } + if (creationFailure != null) { + if (creationFailure instanceof IOException) { + throw new RuntimeException("Unable to create request.", creationFailure); + } else { + throw (RuntimeException) creationFailure; + } + } + try { + return (rawCall = createRawCall()).request(); + } catch (RuntimeException e) { + creationFailure = e; + throw e; + } catch (IOException e) { + creationFailure = e; + throw new RuntimeException("Unable to create request.", e); + } + } + + @Override public void enqueue(final Callback callback) { + checkNotNull(callback, "callback == null"); + + okhttp3.Call call; + Throwable failure; + + synchronized (this) { + if (executed) throw new IllegalStateException("Already executed."); + executed = true; + + call = rawCall; + failure = creationFailure; + if (call == null && failure == null) { + try { + call = rawCall = createRawCall(); + } catch (Throwable t) { + failure = creationFailure = t; + } + } + } + + if (failure != null) { + callback.onFailure(this, failure); + return; + } + + if (canceled) { + call.cancel(); + } + + call.enqueue(new okhttp3.Callback() { + @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) + throws IOException { + Response response; + try { + response = parseResponse(rawResponse); + } catch (Throwable e) { + callFailure(e); + return; + } + callSuccess(response); + } + + @Override public void onFailure(okhttp3.Call call, IOException e) { + try { + callback.onFailure(OkHttpCall.this, e); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private void callFailure(Throwable e) { + try { + callback.onFailure(OkHttpCall.this, e); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private void callSuccess(Response response) { + try { + callback.onResponse(OkHttpCall.this, response); + } catch (Throwable t) { + t.printStackTrace(); + } + } + }); + } + + + private okhttp3.Call createRawCall() throws IOException { + + Request request = serviceMethod.toRequest(args); + okhttp3.Call call = serviceMethod.callFactory.newCall(request); + if (call == null) { + throw new NullPointerException("Call.Factory returned null."); + } + return call; + } + } + + // Call.java + public interface Call extends Cloneable { + + Response execute() throws IOException;//同步调度器 + + void enqueue(Callback callback);//异步调度器 + + boolean isExecuted();//是否是执行 + + void cancel(); + + boolean isCanceled(); + + Call clone(); + + Request request();//创建请求 + } + + + 总结:OkHttpCall 实现了Retrofit自己的Call;并实现request,enqueue,execute方法 + + 1. request方法:内部又调用createRawCall方法 + 内部创建一个Okhttp3的Call(先通过serviceMethod构建一个Okhttp3的request对象,然后在用通过配置的callFactory也就是OkHttpClient创建一个Call) + 2. enqueue方法:通过OKhttp的call对象正常调用请求 + +5. serviceMethod.callAdapter.adapt(okHttpCall);这句中callAdapter默认对应是 + + + //ExecutorCallAdapterFactory.java + new CallAdapter>() { + @Override public Type responseType() { + return responseType; + } + + @Override public Call adapt(Call call) { + return new ExecutorCallbackCall<>(callbackExecutor, call); + } + + + 总结 adapt方法最终返回一个ExecutorCallbackCall对象 + +6. ExecutorCallbackCall + + static final class ExecutorCallbackCall implements Call { + final Executor callbackExecutor; + final Call delegate; + .... + .... + } + + 总结:ExecutorCallbackCall 实现了Retrofit的Call接口,代理了OKHttpCall,进一步代理了OKHttp + +7.最终通过 ExecutorCallbackCall去调用enqueue和execute去执行请求啦 + + + +####总结 + +通过retrofit 初始化一个ExecutorCallAdapterFactory对象,和配置一个Converter.Factory对象用转化数据,然后调用create方法创建一个API代理对象,API代理对象调用接口 +,查找有没有对应ServiceMethod如果没有,就通过method参数创建一个,同时解析method注解和参数封装到ServiceMethod,然后在创建一个OKHttpCall,再调用ServiceMethod保存的callAdapter引用 +(通过ExecutorCallAdapterFactory创建)再调用adapt 返回一个ExecutorCallbackCall ,因为返回一个ExecutorCallbackCall代理了call,然后返回一个ExecutorCallbackCall去执行请求 \ No newline at end of file diff --git a/sources/rxjavademo.md b/sources/rxjavademo.md new file mode 100644 index 0000000..1c2d417 --- /dev/null +++ b/sources/rxjavademo.md @@ -0,0 +1,928 @@ +### rxjava + + + https://github.com/ReactiveX/RxJava + +1. rxjava 2.0 新要求 + +RxJava2.X中,Observeable用于订阅Observer,是不支持背压的,而Flowable用于订阅Subscriber,是支持背压(Backpressure)的。 + + +2. 啥叫不支持背压呢? + + 当被观察者快速发送大量数据时,下游不会做其他处理,即使数据大量堆积,调用链也不会报MissingBackpressureException,消耗内存过大只会OOM + + 我在测试的时候,快速发送了100000个整形数据,下游延迟接收,结果被观察者的数据全部发送出去了,内存确实明显增加了,遗憾的是没有OOM。 + + +3. Observable 、Observer正确使用 + + + + Observable mObservable=Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter e) throws Exception { + e.onNext(1); + e.onNext(2); + e.onComplete(); + } + }); + + Observer mObserver=new Observer() { + //这是新加入的方法,在订阅后发送数据之前, + //回首先调用这个方法,而Disposable可用于取消订阅 + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onNext(Integer value) { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }; + + mObservable.subscribe(mObserver); + + +4. Flowable 处理被压问题 + + + - 查看 Flowable源码你会发现他内部限制了缓存大小,128 + + public abstract class Flowable implements Publisher { + /** The default buffer size. */ + static final int BUFFER_SIZE; + static { + BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128)); + } + + - BackpressureStrategy 背压策略主要有四种: + + BackpressureStrategy.ERROR,前面已经用到过,会在上下游流量不平衡的时候报出MissingBackpressureException的错误。 + + BackpressureStrategy.BUFFER,这种背压策略和Observable没有什么区别,上游可以无限发送,水缸足够大,最后还是会抛出OOM;并且可以发现Flowable里无限发送的话,内存增长的比Observable慢,这是因为Flowable采用响应拉取,难免会损耗些性能。 + + BackpressureStrategy.DROP,水缸里只存储128个事件,剩余的事件舍去,如果下游拉取了事件,则上游当前正在发送的事件在拉取时刻补充进水缸。 + + BackpressureStrategy.LATEST,下游总能获取到最后最新的事件,因为水缸中最后进来的事件总会被新的事件overwrite,所以可以每次拉取总能获得最后或是最新的事件。 + +#### 下面代码产生 OOM + + + + public void demo17() { + Observable + .create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter e) throws Exception { + int i = 0; + while (true) { + i++; + System.out.println("发射---->" + i); + e.onNext(i); + } + } + }) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(Disposable d) { + } + + @Override + public void onNext(Integer integer) { + try { + Thread.sleep(50); + System.out.println("接收------>" + integer); + } catch (InterruptedException ignore) { + } + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + @Override + public void onComplete() { + System.out.println("接收------>完成"); + } + }); + } + +由于下游处理数据的速度(的Thread.sleep(50))赶不上上游发射数据的速度,则会导致背压问题。 + + + +#### 最好解决方案 下面,对其通过可流动做些改进,让其既不会产生背压问题,也不会引起异常或者数据丢失。 + + + + public void demo18() { + Flowable + .create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + int i = 0; + while (true) { + if (e.requested() == 0) continue;//此处添加代码,让flowable按需发送数据 + System.out.println("发射---->" + i); + i++; + e.onNext(i); + } + } + }, BackpressureStrategy.MISSING) + .subscribeOn(Schedulers.newThread()) + .observeOn(Schedulers.newThread()) + .subscribe(new Subscriber() { + private Subscription mSubscription; + + @Override + public void onSubscribe(Subscription s) { + s.request(1); //设置初始请求数据量为1 + mSubscription = s; + } + + @Override + public void onNext(Integer integer) { + try { + Thread.sleep(50); + System.out.println("接收------>" + integer); + mSubscription.request(1);//每接收到一条数据增加一条请求量 + } catch (InterruptedException ignore) { + } + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }); + } + + 下游处理数据的速度Thread.sleep(50)赶不上上游发射数据的速度, + 不同的是,我们在下游onNext(整数整数)方法中,每接收一条数据增加一条请求量, + + mSubscription.request(1) + 在上游添加代码 + + if(e.requested()==0)continue; + 。上游让按需发送数据 + + +4. 其他被观察者/观察者模式 + + 当然,除了上面这两种观察者,还有一类观察者 + - Single/SingleObserver + - Completable/CompletableObserver + - Maybe/MaybeObserver + + + + Single + 只发射一条单一的数据,或者一条异常通知,不能发射完成通知,其中数据与通知只能发射一个。 + + Completable + 只发射一条完成通知,或者一条异常通知,不能发射数据,其中完成通知与异常通知只能发射一个 + + Maybe + 可发射一条单一的数据,以及发射一条完成通知,或者一条异常通知,其中完成通知和异常通知只能发射一个,发射数据只能在发射完成通知或者异常通知之前,否则发射数据无效。 + + + + + - 1. Single使用 + + + + Single.create(new SingleOnSubscribe() { + @Override + public void subscribe(SingleEmitter e) throws Exception { + e.onError(new Exception("自定义异常"));//只会走一个,其他的就会被无效 + e.onSuccess(12); + e.onSuccess(12); + e.onError(new Exception("自定义异常")); + } + }).subscribe(new SingleObserver() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onSuccess(Integer integer) { + + Log.i(tag,"onSuccess integer="+integer); + } + + @Override + public void onError(Throwable e) { + Log.i(tag,"onError"); + e.printStackTrace(); + } + }); + } + + - 2. Completable 使用 + + + + Completable.create(new CompletableOnSubscribe() { + @Override + public void subscribe(CompletableEmitter e) throws Exception { + e.onComplete();//后面的都不会发送 + e.onError(new Exception("自定义异常")); + + + } + + }).subscribe(new CompletableObserver() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onComplete() { + Log.i(tag,"onComplete"); + } + + @Override + public void onError(Throwable e) { + Log.i(tag,"onError"); + } + }); + + 方法onComplete与onError只可调用一个,若先调用onError则会导致onComplete无效 + + - 3. MayBe 使用 + + 示例1:Maybe发射单一数据和完成通知 + + 示例2:Maybe发射单一数据和异常通知 + + 总之和上面一样只不过可以发送两个 + +4. Maybe/MaybeObserver 具体使用 给句上面秒速 发送数据,只能一个 + + + + //判断是否登陆 + + + Maybe.just(isLogin()) + //可能涉及到IO操作,放在子线程 + .subscribeOn(Schedulers.newThread()) + //取回结果传到主线程 + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new MaybeObserver() { + @Override + public void onSubscribe(Disposable d) { + + } + + @Override + public void onSuccess(Boolean value) { + if(value){ + ... + }else{ + ... + } + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onComplete() { + + } + }); + + + + ### 操作符 + + + + 1. unsafeCreate(create) + + 创建一个Observable(被观察者),当被观察者(Observer)/订阅者(Subscriber) + subscribe(订阅)的时候就会依次发出三条字符串数据("Hello","RxJava","Nice to meet you") + 最终onComplete + + + Observable.unsafeCreate(new Observable.OnSubscribe() { + @Override + public void call(Subscriber subscriber) { + subscriber.onNext("Hello"); + subscriber.onNext("RxJava"); + subscriber.onNext("Nice to meet you"); + subscriber.onCompleted(); + } + }); + + + 2. just + + 作用同上,订阅时依次发出三条数据,不过此方法参数可以有1-9条 + + Observable.just("Hello", "RxJava", "Nice to meet you") + + 3. from + + + 作用同just不过是把参数封装成数组或者可迭代的集合在依次发送出来,突破了just9个参数的限制 + + String[] strings = {"Hello", "RxJava", "Nice to meet you"}; + Observable.from(strings) + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + + 4. defer + + + 顾名思义,延迟创建 + + + + private String[] strings1 = {"Hello", "World"}; + private String[] strings2 = {"Hello", "RxJava"}; + + private void test() { + Observable observable = Observable.defer(new Func0>() { + @Override + public Observable call() { + return Observable.from(strings1); + } + }); + + strings1 = strings2; //订阅前把strings给改了 + observable.subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + } + 我们发现数据结果变成这样了 + + onNext--> Hello + onNext--> RxJava + onComplete + 由此可以证明defer操作符起到的不过是一个“预创建”的作用,真正创建是发生在订阅的时候 + +5. empty + 创建一个空的,不会发射任何事件(数据)的Observable + Observable.empty() + .subscribe(new Action1() { + @Override + public void call(Object o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onComplete + + + 6. never + 创建一个不会发出任何事件也不会结束的Observable + + + Observable.never() + .subscribe(new Action1() { + @Override + public void call(Object o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + ······ + + 7. error + + 创建一个会发出一个error事件的Observable + + Observable.error(new RunftimeException("fuck!")) + .subscribe(new Action1() { + @Override + public void call(Object o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onError--> fuck! + + 8. range + + 创建一个发射一组整数序列的Observable + + + Observable.range(3, 8) + .subscribe(new Action1() { + @Override + public void call(Object o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + + onNext--> 3 + + onNext--> 4 + + onNext--> 5 + + onNext--> 6 + + onNext--> 7 + + onNext--> 8 + + onNext--> 9 + + onNext--> 10 + + onComplete + +9. interval + + 创建一个无限的计时序列,每隔一段时间发射一个数字(从0开始)的Observable + + + Observable.interval(1, TimeUnit.SECONDS) + .subscribe(new Action1() { + @Override + public void call(Object o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + + System.in.read();//阻塞当前线程,防止JVM结束程序 + + onNext--> 0 + + onNext--> 1 + + onNext--> 2 + + onNext--> 3 + + onNext--> 4 + + onNext--> 5 + + onNext--> 6 + + ... + +10. buffer(int count) + + 将原发射出来的数据已count为单元打包之后在分别发射出来 + + + Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + .buffer(3) + .subscribe(new Action1() { + @Override + public void call(Object o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> [1, 2, 3] + onNext--> [4, 5, 6] + onNext--> [7, 8, 9] + onNext--> [10] + onComplete + + + +11. map + + 将原Observable发射出来的数据转换为另外一种类型的数据 + + + 1. map处理类型转换 + + 传递一个Function<原本类型,新类型>(){ + + 新类型 apply(原本类型数据){ + // 处理 + return 新类型数据 + } + } + + 2. flatMap 处理多对多类型数据,因为他支持新类型可以还是一个被观察者 + + Function<原本类型,Observerable<新类型>>(){ + + Observerable<新类型> apply(原本类型数据){ + // 处理 + return Observerable.fromXXX(新类型数据)s + } + } + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .map(new Func1() { //泛型第一个类型是原数据类型,第二个类型是想要变换的数据类型 + @Override + public Integer call(String s) { + // 这是转换成了Student类型 + // Student student = new Student(); + // student.setName(s); + // return student; + return s.hashCode(); //将数据转换为了int(取得其hashCode值) + } + }) + .subscribe(new Action1() { + @Override + public void call(Integer o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> 69609650 + onNext--> -1834252888 + onNext--> -1230747480 + onComplete + + +12. flatMap + + 作用类似于map又比map强大,map是单纯的数据类型的转换,而flapMap可以将原数据新的Observables,再将这些Observables的数据顺序缓存到一个新的队列中,在统一发射出来 + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .flatMap(new Func1>() { + @Override + public Observable call(String s) { + return Observable.just(s.hashCode()); + } + }) + .subscribe(new Action1() { + @Override + public void call(Integer o) { + System.out.println("onNext--> " + o); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + } + onNext--> 69609650 + onNext--> -1834252888 + onNext--> -1230747480 + onComplete + + 这里虽然结果和map是相同的,但是过程却是不同的。flatMap是先将原来的三个字符串("Hello","RxJava","Nice to meet you")依次取其hashCode,在利用Observable.just将转换之后的int类型的值在发射出来。map只是单穿的转换了数据类型,而flapMap是转换成了新的Observable了,这在开发过程中遇到嵌套网络请求的时候十分方便。 + + + 13. 过滤filter + + 对发射的数据做一个限制,只有满足条件的数据才会被发射 + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .filter(new Func1() { + @Override + public Boolean call(String s) { + //这里的显示条件是s的长度大于5,而Hello的长度刚好是5 + //所以不能满足条件 + return s.length() > 5; + } + }) + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> RxJava + onNext--> Nice to meet you + onComplete + 14. take 只发射前N项的数据(takeLast与take想反,只取最后N项数据) + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .take(2) + //.taktLast(2) + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + } + onNext--> Hello + onNext--> RxJava + onComplete + +15. skip 发射数据时忽略前N项数据(skpiLast忽略后N项数据) + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .skip(2) + //.skipLast(2) + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + } + onNext--> Nice to meet you + onComplete + + + 16. elementAt + + 获取原数据的第N项数据作为唯一的数据发射出去(elementAtOrDefault会在index超出范围时,给出一个默认值发射出来) + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .elementAtOrDefault(1, "Great") + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> RxJava + onComplete + + 17. distinct 过滤掉重复项 + + + Observable.just("Hello", "Hello", "Hello", "RxJava", "Nice to meet you") + .distinct() + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> Hello + onNext--> RxJava + onNext--> Nice to meet you + onComplete + + 18.组合 startWith 在发射一组数据之前先发射另一组数据 + + + Observable.just("Hello", "RxJava", "Nice to meet you") + .startWith("One", "Two", "Three") + .subscribe(new Action1() { + @Override + public void call(String s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> One + onNext--> Two + onNext--> Three + onNext--> Hello + onNext--> RxJava + onNext--> Nice to meet you + onComplete + + 19.merge 将多个Observables发射的数据合并后在发射 + + + Observable.merge(Observable.just(1, 2, 3), Observable.just(4, 5), + Observable.just(6, 7), Observable.just(8, 9, 10)) + .subscribe(new Action1() { + @Override + public void call(Integer s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> 1 + onNext--> 2 + onNext--> 3 + onNext--> 4 + onNext--> 5 + onNext--> 6 + onNext--> 7 + onNext--> 8 + onNext--> 9 + onNext--> 10 + onComplete + + 20. zip 按照自己的规则发射与发射数据项最少的相同的数据 + + + Observable.zip(Observable.just(1, 8, 7), Observable.just(2, 5), + Observable.just(3, 6), Observable.just(4, 9, 0), new Func4() { + @Override + public Integer call(Integer integer, Integer integer2, Integer integer3, Integer integer4) { + return integer < integer2 ? integer3 : integer4; + } + }) + .subscribe(new Action1() { + @Override + public void call(Integer s) { + System.out.println("onNext--> " + s); + } + }, new Action1() { + @Override + public void call(Throwable throwable) { + System.out.println("onError--> " + throwable.getMessage()); + } + }, new Action0() { + @Override + public void call() { + System.out.println("onComplete"); + } + }); + onNext--> 3 + onNext--> 9 + onComplete + 通过观察以上例子可以发现我们的发射规则是如果发射的第一个数据小于第二个数则发射第三个数据,否则发射第四个数据(我们来验证一下,1确实是小于2的,随意发射的是3;8并不小于5,所以发射的是9,又因为四个发射箱,最少的之后两项,所以最后只发射了两项数据 + +21. doOnNext()的执行在订阅者调用onNext()之前执行,做一些缓存方法 + + +#### map filter doOnNext等方法 执行顺序和设置的顺序一样,他们的操作都是基于上一个结果 diff --git a/sources/segmentLock.md b/sources/segmentLock.md new file mode 100644 index 0000000..6790e83 --- /dev/null +++ b/sources/segmentLock.md @@ -0,0 +1,30 @@ + +### 分段加锁思想。 + + +**电商公司:假如下单时,用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景? +在实际落地生产的时候,分布式锁这个东西保证了数据的准确性,但是他天然并发能力有点弱** + + +假如你现在iphone有1000个库存,那么你完全可以给拆成20个库存段,要是你愿意,可以在数据库的表里建20个库存字段,每个库存段是50件库存,比如stock_01对应50件库存,stock_02对应50件库存。类似这样的,也可以在redis之类的地方放20个库存key。 + +接着,1000个/s 请求,用一个简单的随机算法,每个请求都是随机在20个分段库存里,选择一个进行加锁。 + +每个下单请求锁了一个库存分段,然后在业务逻辑里面,就对数据库或者是Redis中的那个分段库存进行操作即可,包括查库存 -> 判断库存是否充足 -> 扣减库存。 + +相当于一个20毫秒,可以并发处理掉20个下单请求,那么1秒,也就可以依次处理掉20 * 50 = 1000个对iphone的下单请求了。 + +一旦对某个数据做了分段处理之后,有一个坑一定要注意:就是如果某个下单请求,咔嚓加锁,然后发现这个分段库存里的库存不足了,此时咋办? + +这时你得自动释放锁,然后立马换下一个分段库存,再次尝试加锁后尝试处理。 这个过程一定要实现。 + +#### 分布式锁并发优化方案的不足 + +最大的不足,很不方便,实现太复杂。 + +- 首先,你得对一个数据分段存储,一个库存字段本来好好的,现在要分为20个分段库存字段; +- 其次,你在每次处理库存的时候,还得自己写随机算法,随机挑选一个分段来处理; +- 最后,如果某个分段中的数据不足了,你还得自动切换到下一个分段数据去处理。 +这个过程都是要手动写代码实现的,还是有点工作量,挺麻烦的。 + +不过我们确实在一些业务场景里,因为用到了分布式锁,然后又必须要进行锁并发的优化,又进一步用到了分段加锁的技术方案,效果当然是很好的了,一下子并发性能可以增长几十倍 \ No newline at end of file diff --git a/sources/seven_design_principles.md b/sources/seven_design_principles.md new file mode 100644 index 0000000..da30f8c --- /dev/null +++ b/sources/seven_design_principles.md @@ -0,0 +1,232 @@ +#### 面向对象设计七大原则 + +面向对象七大设计原则 + + +1、 开闭原则 + + 核心思想:对扩展开放,对修改关闭。 + 即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,这样兼容性更好 + + 书籍接口: + + public interface IBook{ + public String getName(); + public String getPrice(); + public String getAuthor(); + } + 小说类书籍: + + public class NovelBook implements IBook{ + private String name; + private int price; + private String author; + public NovelBook(String name,int price,String author){ + this.name = name; + this.price = price; + this.author = author; + } + public String getAutor(){ + return this.author; + } + + public String getName(){ + return this.name; + } + + public int getPrice(){ + return this.price; + } + } + Client类: + + public class Client{ + public static void main(Strings[] args){ + IBook novel = new NovelBook("笑傲江湖",100,"金庸"); + System.out.println( + "书籍名字:"+novel.getName()+ + "书籍作者:"+novel.getAuthor()+ + "书籍价格:"+novel.getPrice() + ); + } + } + 项目投入使用后,书籍正常销售,但是我们经常因为各种原因,要打折来销售书籍,这是一个变化,我们要如何应对这样一个需求变化呢? + + 我们有下面三种方法可以解决此问题: + + 修改接口 + 在IBook接口中,增加一个方法getOffPrice(),专门用于进行打折处理,所有的实现类实现此方法。但是对于这样的一个修改方式,首先,作为接口,IBook应该稳定且可靠,不应该经常发生改变,否则接口作为契约的作用就失去了。其次,并不是所有的书籍都需要打折销售,仅仅因为NovelBook打折销售就修改接口使所有书都必须实现打折销售的逻辑,显然与实际业务不符。因此,此方案否定。 + + 修改实现类 + 修改NovelBook类的方法,直接在getPrice()方法中实现打折处理。此方法是有问题的,例如我们如果getPrice()方法中只需要读取书籍的打折前的价格呢?这不是有问题吗?当然我们也可以再增加getOffPrice()方法,这也是可以实现其需求,但是这就有二个读取价格的方法,因此,该方案也不是一个最优方案。 + + 通过扩展实现变化 + 我们可以增加一个子类OffNovelBook(继承自NovelBook),覆写getPrice()方法。此方法修改少,对现有的代码没有影响,风险少,是最好的办法,同时也符合开闭原则。 + + 下面是修改后的类图: + + + + +2、 里氏替换原则 + + 在任何父类出现的地方都可以用他的子类来替代(子类应当可以替换父类,并出现在父类能够出现的任何地方) + + 只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。 + + +3、 单一职责原则 + + 核心:解耦和增强内聚性(高内聚,低耦合) + 类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联, + 改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题。 + + +4、 接口隔离原则 + + 核心思想:不应该强迫客户程序依赖他们不需要使用的方法。 + + 接口分离原则的意思就是:一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口当中. + + 比如使用Retrofit时,我们需要定义ApiService,有时为了方便维护,把所有方法写到一个接口中,这就会再一个模块调用会出现不需要的使用的方法,这就违背了接口隔离原则 + 在开发过程中也要注意取舍 + + 分离接口的两种实现方法: + + 1.使用委托分离接口。(Separation through Delegation) + + 2.使用多重继承分离接口。(Separation through Multiple Inheritance) + +5、 依赖倒置原则 + + 核心:要依赖于抽象,不要依赖于具体的实现 + + 实现方式: + 1.通过构造函数传递依赖对象 + + 2.通过setter方法传递依赖对象 + + 3.接口声明实现依赖对象 + +6、 迪米特原则 + + 又叫最少知识原则 + 核心思想: 一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。 + + (类间解耦,低耦合)意思就是降低各个对象之间的耦合,提高系统的可维护性; + + 而不理会模块的内部工作原理,可以使各个模块的耦合成都降到最低,促进软件的复用 + + 个人理解,一个类的每个方法,自己干自己的事,里面尽量不要引入其他的类和对象 + + 解决方法: + 在模块之间只通过接口来通信 + + + + 只和朋友交流。迪米特还有一个英文解释叫做“Only talk to your immedate friends”,只和直接 的朋友通信,什么叫做直接的朋友呢?每个对象都必然会和其他对象有耦合关系,两个对象之间的耦合就 成为朋友关系,这种关系有很多比如组合、聚合、依赖等等 + + + /** + * + * 首先来看 Teacher 有几个朋友,就一个 GroupLeader 类,这个 就是朋友类,朋友类是怎么定义的呢? + 出现在成员变量、方法的输入输出参数中的类被称为成员朋友类, 迪米特法则说是一个类只和朋友类交流, + 但是 commond 方法中我们与 Girl 类有了交流,声明了一个 List动态数组,也就是与一个陌生的类 Girl 有了交流,这个不好, + */ + public class Teacher { + //老师对学生发布命令, 清一下女生 + public void commond(GroupLeader groupLeader){ + List listGirls = new ArrayList() ; + //初始化女生 + for(int i=0;i<20;i++){ + listGirls.add(new Girl()); + } + //告诉体育委员开始执行清查任务 + groupLeader.countGirls(listGirls); + } + + // 修改后 + public class Teacher { + //老师对学生发布命令, 清一下女生 + public void commond(GroupLeader groupLeader){ + //告诉体育委员开始执行清查任务 + groupLeader.countGirls(); + } + } + + 不要出现 getA().getB().getC().getD() 这种情况(在一种极端的情况下是允许出现这种访问:每一个点号后面的返回类型都相同) + + 迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高,其要求 的结果就是产生了大量的中转或跳转类,类只能和朋友交流,朋友少了你业务跑不起来,朋友多了,你项 目管理就复杂,大家在使用的时候做相互权衡吧 + +7、组合/聚合复用原则 + + + 聚合复用 + class Classes{ + +          privateStudent student; + +          publicClasses(Student student){ + +                    this.student=student; + +         } + + } + 合成复用 + class House{ + +          private Room room; + +          public House(){ + +                room=new Room(); + +           } + +          public void createHouse(){ + +                 room.createRoom(); + +          } + +   } + + + + 为什么使用合成/聚合复用,而不使用继承复用? + + 由于合成或聚合可以将已有对象纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能。这样做的好处有 + + (1)      新对象存取成分对象的唯一方法是通过成分对象的接口。 + + (2)      这种复用是黑箱复用,因为成分对象的内部细节是新对象看不见的。 + + (3)      这种复用支持包装。 + + (4)      这种复用所需的依赖较少。 + + (5)      每一个新的类可以将焦点集中到一个任务上。 + + (6)      这种复用可以再运行时间内动态进行,新对象可以动态地引用与成分对象类型相同的对象。 + + 一般而言,如果一个角色得到了更多的责任,那么可以使用合成/聚合关系将新的责任委派到合适的对象。当然,这种复用也有缺点。最主要的缺点就是通过这种复用建造的系统会有较多的对象需要管理。 + + 2、继承复用 + + 继承复用通过扩展一个已有对象的实现来得到新的功能,基类明显的捕获共同的属性和方法,而子类通过增加新的属性和方法来扩展超类的实现。继承是类型的复用。 + + 继承复用的优点。 + + (1)      新的实现较为容易,因为超类的大部分功能可以通过继承关系自动进入子类。 + + (2)      修改或扩展继承而来的实现较为容易。 + + 继承复用的缺点。 + + (1)      继承复用破坏包装,因为继承将超类的实现细节暴露给了子类。因为超类的内部细节常常对子类是透明的,因此这种复用是透明的复用,又叫“白箱”复用。 + + (2)      如果超类的实现改变了,那么子类的实现也不得不发生改变。因此,当一个基类发生了改变时,这种改变会传导到一级又一级的子类,使得设计师不得不相应的改变这些子类,以适应超类的变化。 + + (3)      从超类继承而来的实现是静态的,不可能在运行时间内发生变化,因此没有足够的灵活性。 + diff --git a/sources/synchronized_lock.md b/sources/synchronized_lock.md new file mode 100644 index 0000000..e18707d --- /dev/null +++ b/sources/synchronized_lock.md @@ -0,0 +1,59 @@ +### Synchronized 与 Lock区别 + +线程总共有5大状态,通过上面第二个知识点的介绍,理解起来就简单了。 + +新建状态:新建线程对象,并没有调用start()方法之前 + +就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。 + +运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态 + +阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态 + +死亡状态:线程执行结束 + +类别 synchronized Lock + +1. 存在层次 + + - synchronized是Java的关键字,在jvm层面上 + - Lock是一个类是一个API接口 + +2. 锁的释放 + - synchronized以获取锁的线程执行完同步代码、释放锁 ,线程执行发生异常,jvm会让线程释放锁 + - Lock 必须在finally中必须释放锁,不然容易造成线程死锁 + +3. 锁状态 + - synchronized 无法判断锁状态 + - Lock 可以通过tryLock判断锁状态 + +4. 性能 + - synchronized少量同步 + - Lock大量同步,可以更细粒度控制 + +5. 锁类型 + - synchronized可重入 不可中断 非公平 + - Lock可重入 可判断 可公平(两者皆可) + + +#### 尽可能去使用synchronized而不要去使用LOCK + +在jdk1.6~jdk1.7的时候给synchronized做了一次优化 + +1、线程自旋和适应性自旋 +我们知道,java’线程其实是映射在内核之上的,线程的挂起和恢复会极大的影响开销。并且jdk官方人员发现,很多线程在等待锁的时候,在很短的一段时间就获得了锁,所以它们在线程等待的时候,并不需要把线程挂起,而是让他无目的的循环,一般设置10次。这样就避免了线程切换的开销,极大的提升了性能。 +而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。 + +2、锁消除 +什么叫锁消除呢?就是把不必要的同步在编译阶段进行移除。 +那么有的小伙伴又迷糊了,我自己写的代码我会不知道这里要不要加锁?我加了锁就是表示这边会有同步呀? +并不是这样,这里所说的锁消除并不一定指代是你写的代码的锁消除,我打一个比方: +在jdk1.5以前,我们的String字符串拼接操作其实底层是StringBuffer来实现的(这个大家可以用我前面介绍的方法,写一个简单的demo,然后查看class文件中的字节码指令就清楚了),而在jdk1.5之后,那么是用StringBuilder来拼接的。 + + +javap –c HelloWorld 可以查看字节码指令 + + +通过指令集 我们可以清晰段看到,其实synchronized映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当一条线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁那么锁计数+1(为什么会加一呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞。当它遇到monitorexit的时候,锁计数器-1,当计数器为0,那么就释放锁。 + +那么有的朋友看到这里就疑惑了,那图上有2个monitorexit呀?马上回答这个问题:上面我以前写的文章也有表述过,synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是发送异常,虚拟机释放。图中第二个monitorexit就是发生异常时执行的流程,这就是我开头说的“会有2个流程存在“。而且,从图中我们也可以看到在第13行,有一个goto指令,也就是说如果正常运行结束会跳转到19行执行 \ No newline at end of file diff --git a/sources/thread_communication_way.md b/sources/thread_communication_way.md new file mode 100644 index 0000000..6ec6ca3 --- /dev/null +++ b/sources/thread_communication_way.md @@ -0,0 +1,62 @@ +### 线程通信方式 + + + +1. 使用synchronized同步方法的 + + + package com.wangpos.datastructure.java.thread + + import kotlin.concurrent.thread + + fun main() { + + val messageBridge = MessageBridge() + + thread(true) { + println(messageBridge.doSomeThing()) + } + thread(true) { + println(messageBridge.doSomeThing2()) + + } + + } + + + class MessageBridge { + + var message: Int = 0 + + @Synchronized + fun doSomeThing(): Int { + message++ + return message + } + + @Synchronized + fun doSomeThing2(): Int { + message++ + return message + } + } + +由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。 + +这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。 + +2. wait/notify机制 + + +3. 管道通信 + + +4. 静态变量 + +5. volatile修饰的变量值直接存在main memory里面 + + + +#### 比如 “编写两个线程,一个线程打印1~25,另一个线程打印字母A~Z,打印顺序为12A34B56C……5152Z,要求使用线程间的通信。” + 这是一道非常好的面试题,非常能彰显被面者关于多线程的功力,一下子就勾起了我的兴趣。这里抛砖引玉,给出7种想到的解法。 + diff --git a/sources/thread_principle.md b/sources/thread_principle.md new file mode 100644 index 0000000..6faa434 --- /dev/null +++ b/sources/thread_principle.md @@ -0,0 +1,49 @@ +### 线程池原理 + + + + /** + * Created by qiyue on 2018/6/14. + */ + + public class USThreadPool { + + private Set threadSet = new HashSet<>(); + + private Queue queue = new LinkedList<>(); + + private static final String TAG = USThreadPool.class.getSimpleName(); + + public USThreadPool(int size){ + + for (int i=0;i requestQueue = new LinkedList(); + + new Thread( new Runnable(){ + public void run(){ + //启动一个新的线程,用一个True的while循环不停的从队列里面获取第一个request并且处理 + while(true){ + if( !requestQueue.isEmpty() ){ + Request request = requestQueue.poll(); + + String response = // 处理request 的 url,这一步将是耗时的操作,省略细节 + + new Handler( Looper.getMainLooper() ).post( request.callback ) + } + } + } + }).start(); + +#### 发送延迟消息 + + //一个消息的类结构,除了runnable,还有一个该Message需要被执行的时间execTime,两个引用,指向该Message在链表中的前任节点和后继节点。 + public class Message{ + public long execTime = -1; + public Runnable task; + public Message prev; + public Message next; + public Message(Runnable runnable, long milliSec){ + this.task = runnable; + this.execTime = milliSec; + } + } + public class MessageQueue{ + //维持两个dummy的头和尾作为我们消息链表的头和尾,这样做的好处是当我们插入新Message时,不需要考虑头尾为Null的情况,这样代码写起来更加简洁,也是一个小技巧。 + //头的执行时间设置为-1,尾是Long的最大值,这样可以保证其他正常的Message肯定会落在这两个点之间。 + private Message head = new Message(null,-1); + private Message tail = new Message(null,Long.MAX_VALUE); + public void run(){ + new Thread( new Runnable(){ + public void run(){ + //用死循环来不停处理消息 + while(true){ + //这里是关键,当头不是dummy头,并且当前时间是大于或者等于头节点的执行时间的时候,我们可以执行头节点的任务task。 + if( head.next != tail && System.currentTimeMillis()>= head.next.execTime ){ + //执行的过程需要把头结点拿出来并且从链表结构中删除 + Message current = head.next; + Message next = current.next; + current.task.run(); + current.next = null; + current.prev =null; + head.next = next; + next.prev = head; + } + } + } + }).start(); + } + public void post(Runnable task){ + //如果是纯post,那么把消息放在最尾部 + Message message = new Message( task, System.currentMilliSec() ); + Message prev = tail.prev; + prev.next = message; + message.prev = prev; + message.next = tail; + tail.prev = message; + } + public void postDelay(Runnable task, long milliSec){ + //如果是延迟消息,生成的Message的执行时间是当前时间+延迟的秒数。 + Message message = new Message( task, System.currentMilliSec()+milliSec); + //这里使用一个while循环去找第一个执行时间在新创建的Message之前的Message,新创建的Message就要插在它后面。 + Message target = tail; + while(target.execTime>= message.execTime){ + target = target.prev; + } + Message next = target.next; + message.prev = target; + target.next = message; + message.next = next; + next.prev = message; + } + } + + + + MessageQueue queue = new MessageQueue(); + //开启queue的while循环 + queue.run(); + queue.post( new Runnable(....) ) + //三秒之后执行 + queue.postDelay( new Runnable(...) , 3*1000 ) + +*上面的post,和postDelay看起来非常眼熟,没错,这个就是安卓里面Handler的经典方法* + +#### 注意延迟只能保证延后,并不是非常准确 + +像上述代码的例子里面,延迟三秒,是不是精确的做到了在当前时间的三秒后运行. 答案当然是NO! + +在这个设计下,我们只能保证: + +假如消息A延迟的秒数为X,当前时间为Y,系统能保证A不会在X+Y之前执行。 这样其实很好理解,因为如果使用队列来执行代码的话,你永远不知道你前面那个Message的执行时间是多少,假如前面的Message执行时间异常的长。。。。那么轮到当前Message执行的时候,肯定会比它自己的execTime偏后。但是这是可接受的。 + +如果我们需要严格让每个Message按照设计的时间执行,那就需要Alarm,类似闹钟的设计了。大家有兴趣可以想想看怎么用最基本的数据结构实现 \ No newline at end of file diff --git a/sources/wait_sleep.md b/sources/wait_sleep.md new file mode 100644 index 0000000..1e3c739 --- /dev/null +++ b/sources/wait_sleep.md @@ -0,0 +1,19 @@ +### Java中wait和sleep方法的区别 + +1. 这两个方法来自不同的类分别是Thread和Object + +2. 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。 + +3. (使用范围)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 + +4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 +sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。 +注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep 而不是t线程 + wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生 + +5. 同时wait一般和while连用 + +如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 + +需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException + diff --git a/sources/weakHashMap.md b/sources/weakHashMap.md new file mode 100644 index 0000000..e4e7ed4 --- /dev/null +++ b/sources/weakHashMap.md @@ -0,0 +1,27 @@ + +### WeakHashMap + +#### 1.问题 + +1. Java WeakHashMap 到底Weak在哪里,它真的很弱吗? +2. WeakHashMap 的适用场景是什么,使用时需要注意些什么? +3. 弱引用和强引用对Java GC有什么不同影响? + + +#### 2.介绍 + + WeakHashMap,从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的entry可能会被GC自动删除,即使程序员没有调用remove()或者clear()方法。 +更直观的说,当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况: + +1. 调用两次size()方法返回不同的值; +2. 两次调用isEmpty()方法,第一次返回false,第二次返回true; +3. 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key; +4. 两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。 + +遇到这么奇葩的现象,你是不是觉得使用者一定会疯掉? + + 其实不然,WeekHashMap 的这个特点特别适用于需要缓存的场景。在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存MISS也不会造成错误,因为可以通过计算重新得到。 + +要明白 WeekHashMap 的工作原理,还需要引入一个概念:弱引用(WeakReference)。我们都知道Java中内存是通过GC自动管理的,GC会在程序运行过程中自动判断哪些对象是可以被回收的,并在合适的时机进行内存释放。GC判断某个对象是否可被回收的依据是,是否有有效的引用指向该对象。如果没有有效引用指向该对象(基本意味着不存在访问该对象的方式),那么该对象就是可回收的。这里的“有效引用”并不包括弱引用。也就是说,虽然弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收。 + +WeakHashMap 内部是通过弱引用来管理entry的,弱引用的特性对应到 WeakHashMap 上意味着什么呢?将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收,除非在 WeakHashMap 之外还有对该key的强引用。 diff --git a/sources/workManager.md b/sources/workManager.md new file mode 100644 index 0000000..f47c8bd --- /dev/null +++ b/sources/workManager.md @@ -0,0 +1,114 @@ + + +### WorkManager + + +1. 简介: 就是 ”管理一些要在后台工作的任务, – 即使你的应用没启动也能保证任务能被执行”。 + +2. 为啥不用AsyncTask, ThreadPool, RxJava? + 这三个和WorkManager并不是替代的关系. 这三个工具, 能帮助你在应用中开后台线程干活, 但是应用一被杀或被关闭, 这些工具就干不了活了。 + 而WorkManager不是, 它在应用被杀, 甚至设备重启后仍能保证你安排给他的任务能得到执行。 + 其实Google自己也说了:”WorkManager并不是为了那种在应用内的后台线程而设计出来的. + 如果你有这种需求你应该使用ThreadPool”。 + +3. 那为何不用JobScheduler, AlarmManger来做? + 其实这个想法很对. WorkManager在底层也是看你是什么版本来选到底是JobScheduler, AlamarManager来做。 + JobScheduler是Android 5.x才有的. 而AlarmManager一直存在. 所以WorkManager在底层, 会根据你的设备情况, 选用JobScheduler, Firebase的JobDispatcher, 或是AlarmManager。 + +#### WorkManager使用 + +1. 导入jar包 + + Kotlin + + implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01" + + Java + + implementation "android.arch.work:work-runtime:1.0.0-alpha01" + +2. 首先创建一个XXXWork继承Work类,实现doWork方法,doWork方法就可以执行你的任务了 + + 1. doWork没有参数,怎么传递参数呢?通过setInputData()和getInputData() + 2. 如何返回数据?通过setOutData()和getOutData() + 3. 结果如何监听?通过 workManager.getStatusById(uuid).observe() + 4. 监听状态有几种? ENQUEUED RUNNING SUCCEEDED + +3.两种work类型,一种执行一次;一种周期性执行 + + //notice 一次的请求 + OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder( + UploadWork.class + ).setConstraints(constraints).setInputData(inputData).build(); + + + //notice 执行定时任务:设置工作的时间间隔 + PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(UploadWork + .class, 1, TimeUnit.MINUTES).addTag(UploadWork.TAG) + .setConstraints(constraints).setInputData(inputData).build(); + +4. 设置约束 + 1. setRequiredNetworkType 网络 + 2. setRequiresBatteryNotLow 电量 + 3. setRequiresCharging 是否充电 + 4. setRequiresDeviceIdle 是否是idle状态 + 5. setRequiresStorageNotLow存储是否低 + +5. workManager优点 + + 1. Easy to schedule + 2. Easy to cancel + 3. Easy to query + 4. Support for all android versions + +6. 示例: + + /** + * 模拟一个网络请求 + */ + private void exeWorkByNetWork() { + //notice 创建约束条件 + Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) + .build(); + //notice 输入数据 + Data inputData = new Data.Builder().putBoolean("isTest", true).build(); + + //notice 构建请求类型,一次的请求 + OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder( + UploadWork.class + ).setConstraints(constraints).setInputData(inputData).build(); + + //notice 设置结果回调 + WorkManager.getInstance().getStatusById(oneTimeWorkRequest.getId()) + .observe(this, new Observer() { + @Override + public void onChanged(@Nullable WorkStatus workStatus) { + + if (workStatus != null && workStatus.getState() == State.SUCCEEDED) { + //notice 取出回调数据 + String result = workStatus.getOutputData().getString("result", ""); + Log.i("result","workStatus="+workStatus.getState()); + toast(result); + + } + } + }); + //notice 执行 + WorkManager.getInstance().enqueue(oneTimeWorkRequest); + + + /** + * 你也可以让多个任务按顺序执行: + + WorkManager.getInstance(this) + .beginWith(Work.from(LocationWork.class)) + .then(Work.from(LocationUploadWorker.class)) + .enqueue(); + 你还可以让多个任务同时执行: + + WorkManager.getInstance(this).enqueue(Work.from(LocationWork.class, + LocationUploadWorker.class)); + */ + } + + diff --git a/sources/yield_join.md b/sources/yield_join.md new file mode 100644 index 0000000..822dac3 --- /dev/null +++ b/sources/yield_join.md @@ -0,0 +1,9 @@ +### yield 和 join 使用 + +1. yield方法 + + 暂停当前正在执行的线程对象。 + + yield()方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。如果没有的话,那么yield()方法将不会起作用,并且由可执行状态后马上又被执行。 + +2. join是Thread方法,join方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。 \ No newline at end of file