diff --git a/db.json b/db.json index e6276b292..81750b96f 100644 --- a/db.json +++ b/db.json @@ -1 +1 @@ -{"meta":{"version":1,"warehouse":"2.2.0"},"models":{"Asset":[{"_id":"source/CNAME","path":"CNAME","modified":1,"renderable":0},{"_id":"source/apple-touch-icon.png","path":"apple-touch-icon.png","modified":1,"renderable":0},{"_id":"source/favicon.ico","path":"favicon.ico","modified":1,"renderable":0},{"_id":"themes/maupassant/source/css/default.css","path":"css/default.css","modified":1,"renderable":1},{"_id":"themes/maupassant/source/css/donate.css","path":"css/donate.css","modified":1,"renderable":1},{"_id":"themes/maupassant/source/css/style.scss","path":"css/style.scss","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/AliPayQR.png","path":"img/AliPayQR.png","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/BTCQR.png","path":"img/BTCQR.png","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/WeChatQR.png","path":"img/WeChatQR.png","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/alipay.svg","path":"img/alipay.svg","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/bitcoin.svg","path":"img/bitcoin.svg","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/like.svg","path":"img/like.svg","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/paypal.svg","path":"img/paypal.svg","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/github.svg","path":"img/github.svg","modified":1,"renderable":1},{"_id":"themes/maupassant/source/img/wechat.svg","path":"img/wechat.svg","modified":1,"renderable":1},{"_id":"themes/maupassant/source/donate/index.html","path":"donate/index.html","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/donate.js","path":"js/donate.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/fancybox.js","path":"js/fancybox.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/codeblock-resizer.js","path":"js/codeblock-resizer.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/search.js","path":"js/search.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/share.js","path":"js/share.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/smartresize.js","path":"js/smartresize.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/totop.js","path":"js/totop.js","modified":1,"renderable":1},{"_id":"themes/maupassant/source/js/gitment.browser.js","path":"js/gitment.browser.js","modified":1,"renderable":1}],"Cache":[{"_id":"source/CNAME","hash":"a7bbae95a61399a38210ff912e7de02b73339957","modified":1615384502848},{"_id":"source/apple-touch-icon.png","hash":"2df3b999532fddb5ba90d74e8d27d0a59eaf3515","modified":1515810562265},{"_id":"themes/maupassant/.travis.yml","hash":"f8da426b97088e4caa5226cff219a5d95087961f","modified":1515810563593},{"_id":"themes/maupassant/LICENSE","hash":"0663fd3a7ea9fc4f4c634b4d73e2da426b536f86","modified":1515810563594},{"_id":"source/favicon.ico","hash":"693538b691d29da58c053058948a44486c45d30c","modified":1515810562269},{"_id":"themes/maupassant/README.md","hash":"0f64166605330bbdee91bc442b05a78bf0dadbb9","modified":1515810563596},{"_id":"themes/maupassant/_config.yml","hash":"2b41f5d0503b668e0ef6af2d4d494cf859e75a57","modified":1524525015639},{"_id":"themes/maupassant/package.json","hash":"5328c4c435cd4a5fe47caae31b9975013cdca5bb","modified":1515810563657},{"_id":"source/_posts/201703092017_object.md","hash":"b609d70bdfaed82d49b544abd164221c827fb260","modified":1515810562144},{"_id":"source/_posts/201703101259_out_of_memory_error.md","hash":"430a09bbd4c955856a30c428c8fa15c0ca86d86f","modified":1515810562147},{"_id":"source/_posts/201703092044_annotation.md","hash":"a4db4114e568bec879335ae6cac1c74963057818","modified":1515810562145},{"_id":"source/_posts/201703101526_maven.md","hash":"7c1dd065196f5cf8dd8d7a0e0d4eef2519ec4c0b","modified":1515810562149},{"_id":"source/_posts/20170315_iframe.md","hash":"735e3cb1967f8bf873d27e4ab159ef307cff4a94","modified":1515810562151},{"_id":"source/_posts/20170326_shiro.md","hash":"81b7856da2b11ec371ee36101cf52559aa001b52","modified":1515810562156},{"_id":"source/_posts/20170504_jenkins.md","hash":"ca421726408d2a52eba2c7409a71b13972bc25ef","modified":1515810562157},{"_id":"source/_posts/20170315_test.md","hash":"38551cf0d1ba45d1347bee106da5ca9e34826259","modified":1515810562152},{"_id":"source/_posts/20170505_line.md","hash":"838e80e3bc4382554131c2fdfb44deaab009fd1c","modified":1515810562159},{"_id":"source/_posts/20170507_c_note.md","hash":"e9df90506e6f367cfe0e97a87f5b291c649d0972","modified":1515810562161},{"_id":"source/_posts/20170615_junit.md","hash":"864b5f63484f6180f3e7d4af0235e576fa2de852","modified":1515810562163},{"_id":"source/_posts/20170619_Mybatis.md","hash":"fd60102a18ee7b66d31a1a03417c33a45859d6cb","modified":1515810562167},{"_id":"source/_posts/20170616_Mybatis.md","hash":"ed51c823be445a4c050883d2f2a953570eb8700c","modified":1615384215236},{"_id":"source/_posts/20170713_qjf.md","hash":"fcc30e9527beea32162dbe7951ce52fb4c376635","modified":1515810562168},{"_id":"source/_posts/20170807_fzf.md","hash":"d55e2b5d5cd597e9852252604a55ce66e52ae00d","modified":1515810562170},{"_id":"source/_posts/20170807_hs.md","hash":"8edc2ec1fa5677183efc0ff3e33038f056645bed","modified":1515810562171},{"_id":"source/_posts/20170807_tx.md","hash":"14ea904720ec0082c44ea4331d55de67db7f824a","modified":1515810562173},{"_id":"source/_posts/20170810_mn.md","hash":"14fdded23c23dd579459281a4b1b6a43c2078e0d","modified":1515810562175},{"_id":"source/_posts/20170812_lb.md","hash":"d100fdb4e1e96900596906fa4be2aebbc114f3d9","modified":1615384215298},{"_id":"source/_posts/20170811_xxb.md","hash":"18b950977475cb1fba01264caf74bd2204e9d4be","modified":1615384215332},{"_id":"source/_posts/20170816_dl.md","hash":"90c6ce4fcd10c4aa2bfed3ae2901d9929030f4ec","modified":1615384215242},{"_id":"source/_posts/20170817_z.md","hash":"ddec8352c9affdcd30fdaeb1bcde5aff2682cf23","modified":1615384215337},{"_id":"source/_posts/20170826_ecs.md","hash":"ff9636012c6fc6e93e1c65b1f2077b6fc6db1d95","modified":1615384215355},{"_id":"source/_posts/20170906_xsecs.md","hash":"7da6981b898e338b69296e1ef75928c85dcfe00f","modified":1615384215372},{"_id":"source/_posts/20170908_hfms.md","hash":"eeb76fddcd642dd80ed24584631f8f77017f7aff","modified":1615384215257},{"_id":"source/_posts/20170911_list_clone.md","hash":"ec74f47f216d3cf410fdc98d16e6cc604c53f003","modified":1615384215395},{"_id":"source/_posts/20170911_syc.md","hash":"e43ea6dc11469375eaac566f7e36fcf4fee5b142","modified":1615384215344},{"_id":"source/_posts/20170918_protocol.md","hash":"5078f97e3d242a0afbd68ffee36699b502398549","modified":1615384215321},{"_id":"source/_posts/20170922_map.md","hash":"488bbc45e4b9d8c6837fc1e157b483ecf1283f46","modified":1615384215310},{"_id":"source/_posts/20170924_JIT.md","hash":"d5e9a9bffe1257cd3356619ac4dc7146bc371c10","modified":1615384215367},{"_id":"source/_posts/20170924_JVM.md","hash":"86b6db268b7522225de9a0edb4474265d23edda5","modified":1615384215327},{"_id":"source/_posts/20170924_JVM2.md","hash":"7505f41a07d7b6de4c3172cb6eb62837de0eee4d","modified":1615384215377},{"_id":"source/_posts/20170924_JVM3.md","hash":"01e1fc2668799f4ab4b07476f1b1c0ff5fe55c78","modified":1615384215316},{"_id":"source/_posts/20171214_crawler.md","hash":"1c21ce9a7fc36147bb00a6adaa0e97c501e2b195","modified":1516249953558},{"_id":"source/_posts/20171219_crawler2.md","hash":"73e9d79a18abedad53e21d141f0276241e614a8c","modified":1615384215361},{"_id":"source/_posts/20171219_crawler3.md","hash":"3f5fd0a5f20df072e6106c898f9f9b797617cb70","modified":1615384215304},{"_id":"source/_posts/20171219_python_error.md","hash":"ad23c45bcf71fa4a97854c92d1f4b1486c6deddb","modified":1615384215268},{"_id":"source/_posts/20171222_crawler4.md","hash":"d830a77d3562376d6f496857882fd830bd97e792","modified":1615384215382},{"_id":"source/_posts/20171225_regular.md","hash":"2276c31dc4557173ebe911c54a2ee6bf8b180c4f","modified":1615384215287},{"_id":"source/_posts/2017_DBCP.md","hash":"7081ac304973a70b4a7876a224c5035b925dac20","modified":1515810562217},{"_id":"source/_posts/2017_Dom4j.md","hash":"2db3a1cc4509ffd70fdce6a226999bc2ecefe751","modified":1515810562218},{"_id":"source/_posts/2017_Hibernate.md","hash":"08d775f8f63051578ce4248d1da896ae9767005a","modified":1515810562220},{"_id":"source/_posts/2017_HotSpot.md","hash":"8c0cfb5e178ac7d32ac8042308945bb758cd47f0","modified":1515810562221},{"_id":"source/_posts/2017_JAVA_runtime.md","hash":"624afb9ee40c7e02b007cca8083bedd9372cf977","modified":1515810562224},{"_id":"source/_posts/2017_Linux.md","hash":"9f6aba3eef89c799f2db00a49c57651de59a8b16","modified":1515810562226},{"_id":"source/_posts/2017_Mongodb_curd.md","hash":"e69e7fd3f2d6673de078bc0ba9d44ad350682d7e","modified":1515810562229},{"_id":"source/_posts/2017_Mongodb.md","hash":"89a1fd55b1c9dbd4a7fa8b5628048826c6f38cbc","modified":1515810562227},{"_id":"source/_posts/2017_Procedure.md","hash":"077fbfca30c7c6589af34adf6fd29dbb40023cda","modified":1515810562231},{"_id":"source/_posts/2017_ThreadLocal.md","hash":"b366ed5a93ecd6d7baf12bceb6319e98619cc667","modified":1515810562233},{"_id":"source/_posts/2017_XPath.md","hash":"6f4f3e865be0aaf5c81a28aa06c793d01dca196f","modified":1515810562235},{"_id":"source/_posts/2017_c3p0.md","hash":"42f9394dfebf7630e0d2c44c8f0eedaf80146644","modified":1515810562237},{"_id":"source/_posts/2017_centos.md","hash":"9a657ca587a54e704cc311a58e654b0b5a58a215","modified":1515810562238},{"_id":"source/_posts/2017_dcd.md","hash":"abbfe06e83590cca8fbbc0e59aec9efd9e795fb6","modified":1515810562240},{"_id":"source/_posts/2017_e.md","hash":"c9cfcbf7777be1f47e0c3910fb2146a1c474af75","modified":1515810562234},{"_id":"source/_posts/2017_java.md","hash":"576182eeccc0d2ab51f57d54b417b92d1185739f","modified":1515810562242},{"_id":"source/_posts/2017_jaxp.md","hash":"2d8d15a6735ab3470931829e9c2a0e64dcc040bd","modified":1515810562244},{"_id":"source/_posts/2017_jaxp2.md","hash":"2f46ba2a91922112d139f6dc4bdd365e9b60f8d4","modified":1515810562245},{"_id":"source/_posts/2017_jdbc_curd.md","hash":"bf2ca55d6152bb91f2fd60e60ffc8737b202a899","modified":1515810562247},{"_id":"source/_posts/2017_jdbc_file.md","hash":"2ab8e8fac7435bc3b6e0b6ba6950b1e3beadd146","modified":1515810562249},{"_id":"source/_posts/2017_jdbc_getConnection.md","hash":"ce552f049bf9aa374202cf649638a15b1012bc84","modified":1615384215390},{"_id":"source/_posts/2017_jdbc_getStatement.md","hash":"231ad915138234d840a8e266c2c933551551f03b","modified":1615384215263},{"_id":"source/_posts/2017_jdbc_reflect.md","hash":"155d1b12cb4c1cfeee6a4f1f205cc7ebd371ec74","modified":1615384215249},{"_id":"source/_posts/2017_jdbc_transaction.md","hash":"f2b53aa64a2f3b0a87ac8933ec46a7f1213946b3","modified":1515810562255},{"_id":"source/_posts/2017_ljc.md","hash":"92da9aecf0aea19f21707715f7bc7d7ff3d24a86","modified":1515810562257},{"_id":"source/_posts/2017_sax.md","hash":"0c1cc88838e9b57241595e962a6ca517da27f6a0","modified":1515810562259},{"_id":"source/_posts/2017_sax2.md","hash":"33f573d060542a8ed7abd3ad419fa9fda02a47cb","modified":1515810562260},{"_id":"source/_posts/2017_three_pools.md","hash":"25742009e60bcc52e4091aeaa7fc828dd8fcb62c","modified":1515810562262},{"_id":"source/_posts/2018030701.md","hash":"5775c82336bec45e5d91d6fe49ce3357a3d2ac21","modified":1615384215280},{"_id":"source/_posts/2018031201.md","hash":"3750f7e37160bf39bfff53e9e768213898c2877d","modified":1615384215349},{"_id":"source/_posts/2018031202.md","hash":"9d02e76970351bcb23341aa3c91d190b3248b17a","modified":1615384215293},{"_id":"source/_posts/2018031301.md","hash":"9ec052595f9da0d0c513e068e5a2636936b1d41a","modified":1615384215274},{"_id":"source/categories/index.md","hash":"f6b05f97bed3a2acfbe0287536d3902991748c58","modified":1515810562268},{"_id":"source/tags/index.md","hash":"7b2f5e85dab833ab31a7762d1105ae65870f4d12","modified":1515810562272},{"_id":"themes/maupassant/languages/de-DE.yml","hash":"25d1d8cd8113045a7603c14af1ea1539fc6456ed","modified":1515810563600},{"_id":"themes/maupassant/languages/en.yml","hash":"8574e8c36f4c72118f27f7ff7353b2a9ceda2a9b","modified":1515810563601},{"_id":"themes/maupassant/languages/es-ES.yml","hash":"3cc9312fbdba4a8f8e8254804121e4724c719bcc","modified":1515810563603},{"_id":"themes/maupassant/languages/fr-FR.yml","hash":"3a50568f200b9c1258415b53727e42c6b6c7ea0b","modified":1515810563604},{"_id":"themes/maupassant/languages/ko.yml","hash":"a454bcec60113507bc1d593a699849822386c196","modified":1515810563606},{"_id":"themes/maupassant/languages/zh-CN.yml","hash":"fc32c57220ecc58cb1920623105ed8901bc4cbcf","modified":1515810563610},{"_id":"themes/maupassant/languages/ru.yml","hash":"36edc014c6aaef367d58929089bf7915375e71a6","modified":1515810563608},{"_id":"themes/maupassant/languages/zh-TW.yml","hash":"34dba7ac67aeb316f629ca73e546fa143cc362d5","modified":1515810563611},{"_id":"themes/maupassant/layout/archive.pug","hash":"e749f047da5eb6449060a724a543ce84e80a2b8b","modified":1515810563643},{"_id":"themes/maupassant/layout/base-without-sidebar.pug","hash":"b0a0ec63ee0225eaa0996d72164202bc9a28a225","modified":1515810563645},{"_id":"themes/maupassant/layout/base.pug","hash":"7451a590db6943edc099dc0e13b317c38686e8fc","modified":1515810563647},{"_id":"themes/maupassant/layout/page.pug","hash":"a21e638d5459120d88e45e8f18a23dc072d9ca07","modified":1515810563650},{"_id":"themes/maupassant/layout/index.pug","hash":"5e7a3cbd1db16b7e9038ce2746ebe1530d349dbd","modified":1515810563649},{"_id":"themes/maupassant/layout/single-column.pug","hash":"8b4b731cdf86379d526821a1fa950bf15ed61f15","modified":1515810563653},{"_id":"themes/maupassant/layout/post.pug","hash":"dbdb26653458f56545d2ad7b807ee036a1d18d88","modified":1515810563652},{"_id":"themes/maupassant/layout/timeline.pug","hash":"04f7efdc45acda1faff409d0f80fa5a0dd2309d0","modified":1515810563655},{"_id":"themes/maupassant/source/css/default.css","hash":"b41d95120f9e64fd4530ae00ceaef09c7ea20818","modified":1515810563661},{"_id":"themes/maupassant/source/css/donate.css","hash":"b41342b45a1fabd35c84001f82bc7caf6583152a","modified":1515810563662},{"_id":"themes/maupassant/source/css/style.scss","hash":"81c11eaaf9a1a6c363a04962fcf6507730676c5d","modified":1515810563664},{"_id":"themes/maupassant/source/img/AliPayQR.png","hash":"7787b5d91cbf0e19a1260df24f7d949771c7d45b","modified":1515810563668},{"_id":"themes/maupassant/source/img/BTCQR.png","hash":"7d1c80f953bfb6f0a37d432b04c936ea165bfd97","modified":1515810563670},{"_id":"themes/maupassant/source/img/WeChatQR.png","hash":"8c41aca7883e5ff714c56556f5fff8e7e7c38093","modified":1515810563673},{"_id":"themes/maupassant/source/img/alipay.svg","hash":"292ea040e865c1d0be259703ff850570b3bdfc97","modified":1515810563674},{"_id":"themes/maupassant/source/img/bitcoin.svg","hash":"eeb2ee8cf44ba5c298baeed84bb90866f4814955","modified":1515810563676},{"_id":"themes/maupassant/source/img/like.svg","hash":"22a2754dc454d7b0321b70914fb2936b8d2ea8ab","modified":1515810563680},{"_id":"themes/maupassant/source/img/paypal.svg","hash":"e916dea1c1bba1bc935510310f65b2c9328a401a","modified":1515810563681},{"_id":"themes/maupassant/source/img/github.svg","hash":"90ba9a3b0dc19e70e742a39b014194f801e00f97","modified":1515810563678},{"_id":"themes/maupassant/source/img/wechat.svg","hash":"30418295bed44bcc4b29076eb7deed49cf4d6c1c","modified":1515810563683},{"_id":"themes/maupassant/source/donate/index.html","hash":"b5fe5a2f2b9fde5e52fd2046007148f85dba8ef1","modified":1515810563666},{"_id":"themes/maupassant/source/js/donate.js","hash":"a9ff8d20f00ba6c216e6a55865c0b47ce6b28f2c","modified":1515810563687},{"_id":"themes/maupassant/source/js/fancybox.js","hash":"8a993c1c4ad40789d2960b682cb2130382a0f26a","modified":1515810563688},{"_id":"themes/maupassant/source/js/codeblock-resizer.js","hash":"c77270e684a60babc1abb7353e700ecdc5a66d30","modified":1515810563685},{"_id":"themes/maupassant/source/js/search.js","hash":"dbda07a03e6edc73f1dc28a068c24a6037b97b56","modified":1515810563694},{"_id":"themes/maupassant/source/js/share.js","hash":"514e726c1efae9f6566600fa0e945b4b9e620f2e","modified":1515810563695},{"_id":"themes/maupassant/source/js/smartresize.js","hash":"150ab1cad40d7ae081b0896b13f7d7cbac4e6338","modified":1515810563697},{"_id":"themes/maupassant/source/js/totop.js","hash":"15de186b089c245fe60766d509b587919f05ff23","modified":1515810563698},{"_id":"themes/maupassant/layout/_partial/after_footer.pug","hash":"2d75cf1d47c530274bdb376e7bc5506e29e9216b","modified":1515810563615},{"_id":"themes/maupassant/layout/_partial/comments.pug","hash":"be961537a2ee430dc131b5c32484786e23280fc5","modified":1515810563616},{"_id":"themes/maupassant/layout/_partial/footer.pug","hash":"92aa15e813bfb411803cc54218feb5410469a9c2","modified":1515810563618},{"_id":"themes/maupassant/layout/_partial/head.pug","hash":"b7e208a6be8d127366573073b3a626e3cc498412","modified":1515810563619},{"_id":"themes/maupassant/layout/_partial/helpers.pug","hash":"9e44f6d32f2449b4109c33118f8285fa2fc7b023","modified":1515810563621},{"_id":"themes/maupassant/layout/_partial/mathjax.pug","hash":"8f41ca38f3ede7070f016e1c48430302bb6d5858","modified":1515810563622},{"_id":"themes/maupassant/layout/_partial/mathjax2.pug","hash":"b95e8538caed65302c17b3a074c5cb2444f0bffb","modified":1515810563624},{"_id":"themes/maupassant/layout/_partial/paginator.pug","hash":"03ad0c49ae6f8a999ae35b38d08e25775f51f52a","modified":1515810563625},{"_id":"themes/maupassant/layout/_partial/post_nav.pug","hash":"b11d9e6000449838b17f508429f29ffb60f53096","modified":1515810563627},{"_id":"themes/maupassant/layout/_partial/tag.pug","hash":"6145b483b271bba05ad1db7c039fe352a768215b","modified":1515810563628},{"_id":"themes/maupassant/layout/_partial/totop.pug","hash":"eb91a3baf9411188c7c8130f63a674f541ca9c81","modified":1515810563630},{"_id":"themes/maupassant/layout/_widget/category.pug","hash":"7707b4c718a935882ee986d0bb0078e50cdbea64","modified":1515810563632},{"_id":"themes/maupassant/layout/_widget/links.pug","hash":"7bc7c17cfd498c3e0c3371cef78f08f1dc25db36","modified":1515810563634},{"_id":"themes/maupassant/layout/_widget/recent_posts.pug","hash":"770b6c41cbf7969ed33adf87eec3be6f50a0911b","modified":1515810563637},{"_id":"themes/maupassant/layout/_widget/recent_comments.pug","hash":"68bae3eb2f80e6127e03faa7ee1b78fb2e70aafc","modified":1515810563635},{"_id":"themes/maupassant/layout/_widget/search.pug","hash":"6aa743486f282545f553a4fad6aae037afe26108","modified":1515810563639},{"_id":"themes/maupassant/layout/_widget/tag.pug","hash":"37f236365b153fc40324391e5a602d6d50014e18","modified":1515810563641},{"_id":"themes/maupassant/source/js/gitment.browser.js","hash":"b15998a45d5f386d30905cfbfbb1658336acbb5b","modified":1515810563692},{"_id":"public/search.xml","hash":"a412f9e436e23edbc08f4627ddffca0ad6e5fcc9","modified":1615385240644},{"_id":"public/content.json","hash":"0f4c187305cc894acbf2a8a044d18ff19b39a095","modified":1615385240645},{"_id":"public/sitemap.xml","hash":"79496db50b16e770821e2671f0550e9f5cebca6b","modified":1615385240646},{"_id":"public/atom.xml","hash":"ab7fc34de2e99eb51c1574dd61941ed925f5cf15","modified":1615385240691},{"_id":"public/tags/index.html","hash":"676387c346eda1f0f84f2bccd2a8766730ada88a","modified":1615385240789},{"_id":"public/categories/index.html","hash":"4c0d9b6f65c7d866a26a836c1859650e717ed192","modified":1615385240789},{"_id":"public/2020/01/15/20171225_regular/index.html","hash":"0ea3e36abfb674bf50ba266533c44e1c8ec23b4b","modified":1615385240789},{"_id":"public/2020/01/15/20170911_syc/index.html","hash":"42c94569d5fb5aa186ce4f3eb6050631c40dd43f","modified":1615385240794},{"_id":"public/2020/01/15/20170810_mn/index.html","hash":"75f6cab2efa36febbd9f31bdf00d6c02f9db59c6","modified":1615385240794},{"_id":"public/2020/01/15/20170615_junit/index.html","hash":"11908cfc20080f6daf479affc7eb345de914a004","modified":1615385240794},{"_id":"public/2017/03/01/2017_e/index.html","hash":"2c6473e91c6095a39fa1f16fb44e364d8ae8e856","modified":1615385240794},{"_id":"public/2017/02/05/2017_centos/index.html","hash":"12bb3526be620517a83fff66f9f3fe4c1a278980","modified":1615385240794},{"_id":"public/categories/java虚拟机/index.html","hash":"016a6c4e2b34416e47b37778179af7b38f5863f8","modified":1615385240794},{"_id":"public/categories/框架相关/index.html","hash":"7cc99cb161787b18560969e16f9b6795eebabce1","modified":1615385240794},{"_id":"public/categories/程序安装与配置/index.html","hash":"727734189b7f0afb7fc40ac9dbd641613f31f426","modified":1615385240794},{"_id":"public/categories/Git/index.html","hash":"dcf428da6600d3c62610db0a985c819ad09ca4af","modified":1615385240795},{"_id":"public/categories/数据结构和算法/page/2/index.html","hash":"e1f8fca07dc9d156d00083a888102671130dd36a","modified":1615385240795},{"_id":"public/categories/BUG解决/index.html","hash":"30455a86b2a446d6e738f9db09c2d0689f1e2acb","modified":1615385240795},{"_id":"public/categories/数据结构和算法/index.html","hash":"53c361beb82f929497bc3a38caaa09d067ee533b","modified":1615385240795},{"_id":"public/categories/框架相关/前端技术/index.html","hash":"c15f7b1d14b39a63bdb996a485a2d56ad87d83dd","modified":1615385240795},{"_id":"public/categories/爬虫/index.html","hash":"9fa787f1f0813cef937fef08709305dffe599ba2","modified":1615385240795},{"_id":"public/categories/计算机网络/index.html","hash":"89884aef0c551c7254cb391d685be44dff08e13f","modified":1615385240795},{"_id":"public/categories/正则/index.html","hash":"063bae2a99d4d2c3adb36a9f28f9e57555b7d0e5","modified":1615385240795},{"_id":"public/categories/DOM操作/index.html","hash":"0b1e582e239c9ed16c543a2a41acf786d072d1c1","modified":1615385240795},{"_id":"public/categories/Linux/index.html","hash":"2310e73827b18e202f4af2945b2cb82bed1303e1","modified":1615385240795},{"_id":"public/categories/数据库/index.html","hash":"205bf9d5c1a00c3e7d92c57713690942e865edaa","modified":1615385240795},{"_id":"public/categories/JDBC/index.html","hash":"378ae8e537dd71b0009153afbd047697693eb58f","modified":1615385240795},{"_id":"public/categories/数据库连接池/index.html","hash":"61ea8ab9a621ebf85f2ccd4047f13f20a3b22820","modified":1615385240795},{"_id":"public/categories/DOM操作/XML/index.html","hash":"81f1d427bd52b535ab20fd357a20a498546370b1","modified":1615385240795},{"_id":"public/categories/python/index.html","hash":"d2062085a2388fea014f778f24c78adf665ea850","modified":1615385240795},{"_id":"public/categories/java语言基础/index.html","hash":"c16dfb61193f05e9ce24e8575c153ac5d3eb233f","modified":1615385240795},{"_id":"public/categories/机器学习/index.html","hash":"b76f28831f9155c05fdc2a6bb12a587c61399ff4","modified":1615385240795},{"_id":"public/categories/框架相关/权限管理/index.html","hash":"a786e871f897fb6a8e35e024e5135987037fbc05","modified":1615385240795},{"_id":"public/categories/多线程/index.html","hash":"08ed30d4f42f9302389f5b1bb4b407a4540aaabc","modified":1615385240795},{"_id":"public/archives/page/2/index.html","hash":"a9e7a15f9b45546e58a7b396e653524960deef31","modified":1615385240795},{"_id":"public/archives/index.html","hash":"b4eb2c8c2fd2d8da3adcc3de067c62bc5327c8b2","modified":1615385240795},{"_id":"public/archives/page/3/index.html","hash":"742da8bc90dcf3b8ebf64facfbc732a6cab7f512","modified":1615385240795},{"_id":"public/archives/page/5/index.html","hash":"d42488115de3fa4a2d6ad7bba300d90b508094d5","modified":1615385240796},{"_id":"public/archives/page/6/index.html","hash":"b2588b5efb24f93c884a37c7cce5408f3b27d935","modified":1615385240796},{"_id":"public/archives/page/4/index.html","hash":"c9d8c6ff4d77b804372f6e2949498746b2d62a50","modified":1615385240796},{"_id":"public/archives/page/7/index.html","hash":"a5eaff812de98a1bbfb54924e51c9fe947403f60","modified":1615385240796},{"_id":"public/archives/page/8/index.html","hash":"54486b33a63fac7010b044f962a0c48777c727f3","modified":1615385240796},{"_id":"public/archives/2017/index.html","hash":"5f55df91fccca2259b1af381d40d00740a9b40cc","modified":1615385240796},{"_id":"public/archives/2017/page/3/index.html","hash":"d577c47cbd6b1eeddf958a3991e239a7d0d6060f","modified":1615385240796},{"_id":"public/archives/2017/page/2/index.html","hash":"23ac518fbabdeadcc50ad03039fac7123f1dc187","modified":1615385240796},{"_id":"public/archives/2017/02/index.html","hash":"796ca69a5e7958b65a5ecf4f0bf040373eeb551e","modified":1615385240796},{"_id":"public/archives/2017/02/page/2/index.html","hash":"7365a7b60fe9870622c5048b52d79afd51afba52","modified":1615385240796},{"_id":"public/archives/2017/02/page/3/index.html","hash":"58b09da91e9ec4b300a55f1a492dc94889a3412f","modified":1615385240796},{"_id":"public/archives/2017/06/index.html","hash":"17545b7e0233cb8d073405d83e13022de34b8c4f","modified":1615385240797},{"_id":"public/archives/2020/index.html","hash":"7369f78da3a0000c6f92e8a5ac605b338aa985c4","modified":1615385240797},{"_id":"public/archives/2017/03/index.html","hash":"7f1f3603db23bbb73fc7b92849fd88099c86cbdb","modified":1615385240797},{"_id":"public/archives/2020/page/2/index.html","hash":"b5a061cbe680b0a8f2b22ceecfe7b1cce9ac0964","modified":1615385240797},{"_id":"public/archives/2020/page/3/index.html","hash":"edd0c94a5e2c5068a6fa6a121b15fd9ce4e1b777","modified":1615385240797},{"_id":"public/archives/2020/page/4/index.html","hash":"9b28de47352a0631b42ffa315ff0f6e370bc9cbb","modified":1615385240797},{"_id":"public/archives/2020/01/index.html","hash":"4210917becfd5e679695cde1f78de63ee38e10a9","modified":1615385240797},{"_id":"public/archives/2020/page/5/index.html","hash":"6f198be931d6b1ba4fb7dfac15f20d88c173b020","modified":1615385240797},{"_id":"public/archives/2020/01/page/2/index.html","hash":"6e728811e4eb8c7c974fe669ffec82f910850de7","modified":1615385240797},{"_id":"public/archives/2020/01/page/3/index.html","hash":"b244b4dacc59cc47206b8340be5f35c6e791c8b1","modified":1615385240797},{"_id":"public/archives/2020/01/page/4/index.html","hash":"ebe0ea3cdc71e1be7caf6e454a195e6ed99b1656","modified":1615385240797},{"_id":"public/tags/深入了解java虚拟机/index.html","hash":"fade71c618eb187df5fa89f28eb7a14033db236b","modified":1615385240798},{"_id":"public/archives/2020/01/page/5/index.html","hash":"a6a2c5bf067520c8b62e899433be12630d3f19b4","modified":1615385240798},{"_id":"public/tags/java/index.html","hash":"aeb04b9ba2eda99cf987be5221b1490767b46115","modified":1615385240798},{"_id":"public/tags/HTML/index.html","hash":"2d5a31adf01d42d200ac156ab01a33453c9f4c55","modified":1615385240798},{"_id":"public/tags/iframe/index.html","hash":"4f2296268cd70c43a2cd89dcd9fef1cc931983a8","modified":1615385240798},{"_id":"public/tags/Hexo优化/index.html","hash":"17d9db961ef1dbc2fcadcd660480899af28371b8","modified":1615385240798},{"_id":"public/tags/jenkins/index.html","hash":"c55a55e2b5e7822e7718f219088a7a1b4986aacc","modified":1615385240798},{"_id":"public/tags/Git/index.html","hash":"dcf428da6600d3c62610db0a985c819ad09ca4af","modified":1615385240798},{"_id":"public/tags/Maven/index.html","hash":"c726e2f896826af80e1b0848f8f821869f62960c","modified":1615385240798},{"_id":"public/tags/maven/index.html","hash":"c0e273df250effef1449fc39ab593c80a9ffde66","modified":1615385240798},{"_id":"public/tags/C语言/index.html","hash":"0f5d837d8eeabdb3e7407ff2b9df359f3f49e761","modified":1615385240798},{"_id":"public/tags/C语言/page/2/index.html","hash":"67215552a6bfa92ed3cc7269c044947dcd6a2234","modified":1615385240798},{"_id":"public/tags/junit/index.html","hash":"90e5e748d6383efcb4970b84ff7d42168cd9c799","modified":1615385240798},{"_id":"public/tags/定义/index.html","hash":"315ac757d459cd482d99e6d700972660d1c79e2f","modified":1615385240799},{"_id":"public/tags/python/index.html","hash":"269f5e30ba2e4bb6a683520cad94a63e4eceaef6","modified":1615385240799},{"_id":"public/tags/爬虫/index.html","hash":"9fa787f1f0813cef937fef08709305dffe599ba2","modified":1615385240799},{"_id":"public/tags/转码/index.html","hash":"4e442eb24fa7f5130359e66183048f52db7713f2","modified":1615385240799},{"_id":"public/tags/常用命令/index.html","hash":"f68b80d4435b04a25f17e226e2475cb3952ab491","modified":1615385240799},{"_id":"public/tags/正则表达式/index.html","hash":"d62965587be0e732d38bb7014f6ef1142c2af570","modified":1615385240799},{"_id":"public/tags/Dom4j/index.html","hash":"48fe57f40b402a59b01401ef5cfd14520cda3fee","modified":1615385240799},{"_id":"public/tags/Mongodb/index.html","hash":"574fec84b7965904f1b064e9779a13f6d5d9ef7b","modified":1615385240799},{"_id":"public/tags/mysql/index.html","hash":"b16af0806b44305ae1bd29b66e54ee1e49df67e4","modified":1615385240799},{"_id":"public/tags/存储过程/index.html","hash":"3b7ebe79119caec50454bb631344bfa0e95f642f","modified":1615385240799},{"_id":"public/tags/shell/index.html","hash":"4b2b85bf34d54ea1b95213cc28acd78a1e9f39ea","modified":1615385240799},{"_id":"public/2020/01/15/2018031301/index.html","hash":"f589f746c206096581646212b547bd81b92bf211","modified":1615385240799},{"_id":"public/2020/01/15/2018031202/index.html","hash":"b241a027e2669805142a89ed5edc68b19b765f3e","modified":1615385240799},{"_id":"public/2020/01/15/2018031201/index.html","hash":"20a67a0b3feae77cc8aa5da17cda9e5c01879eb1","modified":1615385240799},{"_id":"public/2020/01/15/2018030701/index.html","hash":"c445935243683ea84bd8bae734f956c66c5dd808","modified":1615385240799},{"_id":"public/2020/01/15/20171222_crawler4/index.html","hash":"2b95667d08274b69067524ec81401492c3980055","modified":1615385240799},{"_id":"public/2020/01/15/20171219_python_error/index.html","hash":"cdb17078167c1b3f5b2e647c44994edfb8384000","modified":1615385240799},{"_id":"public/2020/01/15/20171219_crawler3/index.html","hash":"2b8718b02d2193efcbeb83defc922e9fdd7ca34e","modified":1615385240799},{"_id":"public/2020/01/15/20171219_crawler2/index.html","hash":"b7191a3b51bb9d1b79c2472a7fc070c865115eee","modified":1615385240799},{"_id":"public/2020/01/15/20171214_crawler/index.html","hash":"326fc3c4bd48bafe65861d2da8036841fb5f97f7","modified":1615385240799},{"_id":"public/2020/01/15/20170924_JVM3/index.html","hash":"6028b7deded7e499f55b18f2872ba8da60315399","modified":1615385240799},{"_id":"public/2020/01/15/20170924_JVM2/index.html","hash":"7193150a283f7f865b79af9a95a66dc9f00c7adf","modified":1615385240800},{"_id":"public/2020/01/15/20170924_JVM/index.html","hash":"09951e9cbe613eb041cdeda7f203169b3a1e41bf","modified":1615385240800},{"_id":"public/2020/01/15/20170924_JIT/index.html","hash":"eab73eb237b8e3cbd35e95845e6161a55e2bfcbc","modified":1615385240800},{"_id":"public/2020/01/15/20170922_map/index.html","hash":"9c2f31f3220bf3b10c4e17ce449edb0a97e041b2","modified":1615385240800},{"_id":"public/2020/01/15/20170918_protocol/index.html","hash":"84b3cd47b63212f155fd811adc1ee1724b392ba4","modified":1615385240800},{"_id":"public/2020/01/15/20170911_list_clone/index.html","hash":"a425037c9e29438616046e463ded06944b23f201","modified":1615385240800},{"_id":"public/2020/01/15/20170908_hfms/index.html","hash":"19b7e482cd5e76448106a440a56e524ba04d3955","modified":1615385240800},{"_id":"public/2020/01/15/20170826_ecs/index.html","hash":"6602b0d37136dc4f649b59f5712ef777478e607b","modified":1615385240800},{"_id":"public/2020/01/15/20170817_z/index.html","hash":"37303cd98fa572ed502b4eef9e795541ce581bfe","modified":1615385240800},{"_id":"public/2020/01/15/20170906_xsecs/index.html","hash":"40ff4ed351a2d2db017ca3983062a4b7a08714d8","modified":1615385240800},{"_id":"public/2020/01/15/20170816_dl/index.html","hash":"7b78a8af37d7b352d58e72991596198586816dfd","modified":1615385240800},{"_id":"public/2020/01/15/20170812_lb/index.html","hash":"ef23d445447b50bf11a27122e44ecd3d7bb44347","modified":1615385240800},{"_id":"public/2020/01/15/20170807_tx/index.html","hash":"2fee71f3bbd32919c0d7a023c2b716f15f0545f2","modified":1615385240800},{"_id":"public/2020/01/15/20170811_xxb/index.html","hash":"76abaf7cedb406006495c00a6ba2850459e808c4","modified":1615385240801},{"_id":"public/2020/01/15/20170807_hs/index.html","hash":"cce8b821674c9e1e36d5fb606459a2587d2c02ec","modified":1615385240801},{"_id":"public/2020/01/15/20170807_fzf/index.html","hash":"f77ad50a4ace6bd09683554ac08cc5ce29d3ccbc","modified":1615385240801},{"_id":"public/2020/01/15/20170713_qjf/index.html","hash":"128a0c40472463624283a254df0154074a254e3d","modified":1615385240801},{"_id":"public/2020/01/15/20170507_c_note/index.html","hash":"bd29ac14d9a4387794428e4a52703cfd30d8bf35","modified":1615385240801},{"_id":"public/2020/01/15/20170326_shiro/index.html","hash":"cbe669091e66787d1decadd863de70cc9e91a431","modified":1615385240801},{"_id":"public/2020/01/15/20170505_line/index.html","hash":"d3f40f879a00c6b78fe176a42c26c0e3b54da781","modified":1615385240801},{"_id":"public/2020/01/15/20170504_jenkins/index.html","hash":"d59354353558463fbe1e16741e1b9f8e818ce0b3","modified":1615385240801},{"_id":"public/2020/01/15/20170315_test/index.html","hash":"326cd90698ba1b9573875a8361831f8239a6290c","modified":1615385240801},{"_id":"public/2020/01/15/201703101526_maven/index.html","hash":"910d3fba0865c36f15760dd285ec5951cc13f73d","modified":1615385240801},{"_id":"public/2020/01/15/20170315_iframe/index.html","hash":"6b46693ed3e30716477d370035cd13f77eecd131","modified":1615385240801},{"_id":"public/2020/01/15/201703101259_out_of_memory_error/index.html","hash":"d797098802a8e4c863f6058ba5895169f5d0afd8","modified":1615385240801},{"_id":"public/2020/01/15/201703092044_annotation/index.html","hash":"2c4c6a609c9a8932190818877f580195a984bbf1","modified":1615385240801},{"_id":"public/2020/01/15/201703092017_object/index.html","hash":"73993441f34ba2e67c4b0cb9a9382c7151d699c2","modified":1615385240801},{"_id":"public/2017/06/19/20170619_Mybatis/index.html","hash":"317831819b7661f510e2f57404743b8a56280491","modified":1615385240801},{"_id":"public/2017/06/16/20170616_Mybatis/index.html","hash":"404b41f7b698bfe4dbf5d9e9a74bc518b2c3737b","modified":1615385240801},{"_id":"public/2017/03/01/2017_Mongodb_curd/index.html","hash":"c73cc60bf46d2c69fef0e2fc636108695368b44c","modified":1615385240801},{"_id":"public/2017/03/01/2017_Mongodb/index.html","hash":"b2fb617a5cd0688b31487f0d180ab7b32e049e74","modified":1615385240801},{"_id":"public/2017/02/27/2017_Hibernate/index.html","hash":"44137c0a84273464cc3836bce55099595f4a56b7","modified":1615385240801},{"_id":"public/2017/02/15/2017_ThreadLocal/index.html","hash":"6ed0717b266c056eff2011941675abe79da2ccd1","modified":1615385240802},{"_id":"public/2017/02/14/2017_dcd/index.html","hash":"f868b729d0210693524b832cd0896791968456dc","modified":1615385240802},{"_id":"public/2017/02/13/2017_three_pools/index.html","hash":"f8b97197b533f475cfc7bab14ea1a2f728af1478","modified":1615385240802},{"_id":"public/2017/02/14/2017_c3p0/index.html","hash":"9497888c45df48c720001b9cc096f885bffb1665","modified":1615385240802},{"_id":"public/2017/02/12/2017_ljc/index.html","hash":"2e205481cb9d4f6258b83539dc38bd78226d73b4","modified":1615385240803},{"_id":"public/2017/02/12/2017_DBCP/index.html","hash":"77e73226b87828fd944dc37efb966f38cc23ccd3","modified":1615385240803},{"_id":"public/2017/02/10/2017_jdbc_transaction/index.html","hash":"821d5c5990f752b4e5da3f3a2de3ec0369e6327e","modified":1615385240803},{"_id":"public/2017/02/10/2017_jdbc_file/index.html","hash":"1f931a7070ea6a08d6ca22150ca4f28247cc29a5","modified":1615385240803},{"_id":"public/2017/02/09/2017_Linux/index.html","hash":"c426e1695ff4e3a3889a5cbc38fee5566beb4546","modified":1615385240803},{"_id":"public/2017/02/08/2017_jdbc_reflect/index.html","hash":"c7df0cbfaecf23f68f183403e2abae9df175fa17","modified":1615385240803},{"_id":"public/2017/02/07/2017_jdbc_curd/index.html","hash":"caa7d56e1bf1eee038adc757f92ef9d2318e0ed8","modified":1615385240803},{"_id":"public/2017/02/06/2017_jdbc_getConnection/index.html","hash":"269cfc99b85c84d0e531be01f367135967d8eabd","modified":1615385240803},{"_id":"public/2017/02/06/2017_jdbc_getStatement/index.html","hash":"33262dc4db94a1a6fb185caa899d344e9fe31563","modified":1615385240803},{"_id":"public/2017/02/05/2017_jaxp/index.html","hash":"e7486c39ab75bfbc9175306d8cedd9b2225ed339","modified":1615385240804},{"_id":"public/2017/02/05/2017_Procedure/index.html","hash":"aefe945280f4a2a6137b73b51faa0d538f5e7975","modified":1615385240804},{"_id":"public/2017/02/05/2017_jaxp2/index.html","hash":"7f0e7c8322c2b83c875c6afffb4e63eb0f947824","modified":1615385240804},{"_id":"public/2017/02/05/2017_sax/index.html","hash":"f7b5ec252120f6038d1583ded08cefb6ca1bf83c","modified":1615385240804},{"_id":"public/2017/02/05/2017_XPath/index.html","hash":"6744e2f8e7255bd21bacbdc0d420e53e5d4c3b84","modified":1615385240804},{"_id":"public/2017/02/05/2017_HotSpot/index.html","hash":"35b36cbd0485176ed503ef29f11c6b07498c8f4c","modified":1615385240804},{"_id":"public/2017/02/05/2017_JAVA_runtime/index.html","hash":"8c959d4344a0676831904e083b460580f8db2134","modified":1615385240804},{"_id":"public/2017/02/05/2017_Dom4j/index.html","hash":"331938eb6983bacb5318af397d87aacf4842de8f","modified":1615385240804},{"_id":"public/2017/02/05/2017_java/index.html","hash":"64f120d53a318e15b46ff87cc1a6fe0d3110f1e8","modified":1615385240804},{"_id":"public/2017/02/05/2017_sax2/index.html","hash":"ba3a547c05df42fdbb2ed6ff9c92e9194052f431","modified":1615385240804},{"_id":"public/tags/XPath/index.html","hash":"903be7be42d61f8076c426bdf0fa9013b86c6f87","modified":1615385240821},{"_id":"public/tags/C3P0/index.html","hash":"631c8676b90bfd831ffb387dedaceb4c31989873","modified":1615385240821},{"_id":"public/tags/DBCP/index.html","hash":"d8c262a2656ade923a3e15f0560612ef1a0e0c79","modified":1615385240821},{"_id":"public/tags/DRUID/index.html","hash":"992a82e7ec30b358669642a645c6299db10d931e","modified":1615385240821},{"_id":"public/tags/工具类/index.html","hash":"6db80f59cf8b68b97bc962746bb5157ffaa71a8a","modified":1615385240821},{"_id":"public/tags/Vue/index.html","hash":"f8cf2bd01b259235ba21ea47fcf6773e1e01ac0d","modified":1615385240821},{"_id":"public/tags/jaxp/index.html","hash":"a4dad01cbd1c91b993f8a28a0be974bb78ff661b","modified":1615385240821},{"_id":"public/tags/CRUD/index.html","hash":"d9442c35724a14e9abb86fd007c07627c42a5e4f","modified":1615385240822},{"_id":"public/tags/Blob/index.html","hash":"593e3a0f2ea036234fb62b15f610254536661523","modified":1615385240822},{"_id":"public/tags/JDBC/index.html","hash":"0dca96c9c075daad4cfdd010072140baf949970a","modified":1615385240822},{"_id":"public/tags/事务/index.html","hash":"22e32fe27b408959befe59a6981ef97c7ec6e3ad","modified":1615385240822},{"_id":"public/tags/sax/index.html","hash":"36de9c01767d3a57cb90f0397d768ca5a55a6866","modified":1615385240822},{"_id":"public/tags/注解/index.html","hash":"e5190048e6d587a7f4f726c27dc5b94cc4c78295","modified":1615385240822},{"_id":"public/tags/元数据/index.html","hash":"63e1025b5909ab44e7f895769b5b84a1506eae3b","modified":1615385240822},{"_id":"public/tags/Hibernate/index.html","hash":"f630da1bc846486a2bf28df8f6665f563b1937fe","modified":1615385240822},{"_id":"public/tags/机器学习/index.html","hash":"c46e5372e56b47cd14227256cbe9a7d7886014db","modified":1615385240822},{"_id":"public/tags/Mybatis/index.html","hash":"b5ff18201d7fa7359068976076de78c47292ec16","modified":1615385240822},{"_id":"public/tags/ThreadLocal/index.html","hash":"b647f93fe6006e9e8cadf6e2821470cc16e0a877","modified":1615385240822},{"_id":"public/tags/逻辑回归/index.html","hash":"493cf7afd0609968c58f6ea438df6ad87cb6b84b","modified":1615385240822},{"_id":"public/tags/Shiro/index.html","hash":"9c77c707eec10cbf82b945282963fbe822f6136e","modified":1615385240822},{"_id":"public/page/8/index.html","hash":"9803f49c98db9667e571169f8f32ef5ce7028fe1","modified":1615385240822},{"_id":"public/index.html","hash":"a055cbe1b3b98bdb5daa8f3e5312634d85640f95","modified":1615385240822},{"_id":"public/page/2/index.html","hash":"187cc70975f399894439761b282c069706148120","modified":1615385240822},{"_id":"public/page/3/index.html","hash":"bc74a1f351f9a0e29e71dfdb72279340795ab8d9","modified":1615385240822},{"_id":"public/page/4/index.html","hash":"bb76c3c268741ee7f3a58556b86e3b48191e98c1","modified":1615385240822},{"_id":"public/page/5/index.html","hash":"4b0aaf5b056f1e00cd03cf6abe2dfea0725892c8","modified":1615385240822},{"_id":"public/page/7/index.html","hash":"835a414d065653e50e6ff6424ab9599ea8736ca6","modified":1615385240822},{"_id":"public/page/6/index.html","hash":"b898d35439263e29ac6f6f6fec589e9aaa104883","modified":1615385240822},{"_id":"public/CNAME","hash":"a7bbae95a61399a38210ff912e7de02b73339957","modified":1615385240851},{"_id":"public/img/BTCQR.png","hash":"7d1c80f953bfb6f0a37d432b04c936ea165bfd97","modified":1615385240851},{"_id":"public/img/AliPayQR.png","hash":"7787b5d91cbf0e19a1260df24f7d949771c7d45b","modified":1615385240851},{"_id":"public/img/alipay.svg","hash":"292ea040e865c1d0be259703ff850570b3bdfc97","modified":1615385240851},{"_id":"public/apple-touch-icon.png","hash":"2df3b999532fddb5ba90d74e8d27d0a59eaf3515","modified":1615385240851},{"_id":"public/img/bitcoin.svg","hash":"eeb2ee8cf44ba5c298baeed84bb90866f4814955","modified":1615385240851},{"_id":"public/img/WeChatQR.png","hash":"8c41aca7883e5ff714c56556f5fff8e7e7c38093","modified":1615385240851},{"_id":"public/img/paypal.svg","hash":"e916dea1c1bba1bc935510310f65b2c9328a401a","modified":1615385240851},{"_id":"public/img/like.svg","hash":"22a2754dc454d7b0321b70914fb2936b8d2ea8ab","modified":1615385240851},{"_id":"public/img/wechat.svg","hash":"30418295bed44bcc4b29076eb7deed49cf4d6c1c","modified":1615385240851},{"_id":"public/favicon.ico","hash":"693538b691d29da58c053058948a44486c45d30c","modified":1615385240851},{"_id":"public/img/github.svg","hash":"90ba9a3b0dc19e70e742a39b014194f801e00f97","modified":1615385240851},{"_id":"public/js/donate.js","hash":"89f0b9d9d0c4fce183161d29c2a44aef750efb27","modified":1615385240946},{"_id":"public/js/codeblock-resizer.js","hash":"5d0b786d60bf225d9eabcc9cece2719ff4d9b6cd","modified":1615385240946},{"_id":"public/js/fancybox.js","hash":"13c4781570339f4fba76a3d7f202e442817dd605","modified":1615385240946},{"_id":"public/js/share.js","hash":"a2f9de374523dc7f2ddb90ed5f24b668c20d9272","modified":1615385240946},{"_id":"public/js/search.js","hash":"0c0630e2ef213701d393b041f10572e951a27985","modified":1615385240946},{"_id":"public/js/totop.js","hash":"7dbf8fcf582a4fb6eb9b2c60d6de9f9c2091ec4c","modified":1615385240946},{"_id":"public/js/smartresize.js","hash":"3ef157fd877167e3290f42c67a624ea375a46c24","modified":1615385240946},{"_id":"public/css/donate.css","hash":"0dfb45c5bc4215dbff768a46f99a47875ef25b2b","modified":1615385240946},{"_id":"public/donate/index.html","hash":"ad597ef7fe11b8a3acd376e15b8f8489bf2ebc26","modified":1615385240947},{"_id":"public/css/default.css","hash":"f5823ac749d98f6fddc33939bb1b752747ceb925","modified":1615385241047},{"_id":"public/css/style.css","hash":"95578b350315eb70e4f6f245a1bb3381c120b9f2","modified":1615385241136},{"_id":"public/js/gitment.browser.js","hash":"376446d9c5930576016f97dd63e5e6616c94d8d4","modified":1615385241241}],"Category":[{"name":"java虚拟机","_id":"ckm3inupd000124ujh4z2s9pn"},{"name":"框架相关","_id":"ckm3inuur000d24ujksl5yti3"},{"name":"程序安装与配置","_id":"ckm3inuuv000k24ujagxftan4"},{"name":"Git","_id":"ckm3inuuz000o24ujlm33363v"},{"name":"前端技术","parent":"ckm3inuur000d24ujksl5yti3","_id":"ckm3inuv4000v24ujqizcjifq"},{"name":"数据结构和算法","_id":"ckm3inuv7001124ujwzi3wfif"},{"name":"BUG解决","_id":"ckm3inuvb001824uj5gsd1eel"},{"name":"计算机网络","_id":"ckm3inuw1002g24ujl12c78vp"},{"name":"爬虫","_id":"ckm3inuw5002o24ujp8jy26us"},{"name":"DOM操作","_id":"ckm3inuw9002x24ujhgiwgxys"},{"name":"正则","_id":"ckm3inuwe003524ujc6yff7mp"},{"name":"Linux","_id":"ckm3inuwi003e24uj6otnpwyb"},{"name":"数据库","_id":"ckm3inuwn003m24uj0rkfh3ka"},{"name":"JDBC","_id":"ckm3inuwz004724ujuzfau4n7"},{"name":"XML","parent":"ckm3inuw9002x24ujhgiwgxys","_id":"ckm3inux1004e24uj8qyzq1w3"},{"name":"数据库连接池","_id":"ckm3inuxc005724ujcqqpqw5q"},{"name":"python","_id":"ckm3inuxg005i24ujqhdch274"},{"name":"java语言基础","_id":"ckm3invds007424ujez58pt7x"},{"name":"机器学习","_id":"ckm3invek008m24uj815h174b"},{"name":"多线程","_id":"ckm3invgs009924ujexuf8q76"},{"name":"权限管理","parent":"ckm3inuur000d24ujksl5yti3","_id":"ckm3invhj009p24ujfqp59o3x"}],"Data":[],"Page":[{"layout":"tags","title":"tags","_content":"","source":"tags/index.md","raw":"layout: tags \ntitle: tags \n---","date":"2020-02-19T01:26:08.543Z","updated":"2018-01-13T02:29:22.272Z","path":"tags/index.html","comments":1,"_id":"ckm3inuum000824uj0p80ev1w","content":"","site":{"data":{}},"excerpt":"","more":""},{"layout":"categories","title":"categories","_content":"","source":"categories/index.md","raw":"layout: categories\ntitle: categories\n---","date":"2020-02-19T01:26:08.541Z","updated":"2018-01-13T02:29:22.268Z","path":"categories/index.html","comments":1,"_id":"ckm3inuuo000a24ujum9f1ijz","content":"","site":{"data":{}},"excerpt":"","more":""}],"Post":[{"title":"对象在HotSpot虚拟机中","comments":1,"description":"对象在内存的布局,访问定位","_content":"\n## 对象的建立位置\n### 对象在HotSpot虚拟机内存中存储分为三个区域\n 1.对象头(Hearder)\n 头中包含两部分信息:对象的运行时数据(官方叫Mark Word)以及类型指针;\n 2.实例数据(Instance Data)\n 3.对齐填充(Padding)\n\n> ###### 对象运行时数据有很多,但是对象头信息是与数据无关的额外信息,为了提高空间利用率,其数据结构不固定,会根据对象状态复用自身的存储空间\n> ###### **类型指针**是对象指向其类元数据的指针,虚拟机通过其确定对象是哪个类的实例(不是每个对象都有)\n> ###### 如果是数组,对象头中还有一块区域存放数组长度(一般对象可根据类型获得)\n\n> ###### 实例数据是真正的对象的有效信息,这些信息的顺序受到虚拟机分配策略参数的影响\n 默认分配策略: \n longs/doubles,ints,shorts/chars,bytes/booleans/oops(一般对象指针)\n 父类定义的变量在子类之前\n 相同宽度的字段总是被分配的一起\n\n\n\n## 对象的访问(方式)\n 1.句柄方式: \n 虚拟机在java堆中划分出一块叫句柄池,reference中存储对象的句柄地址,句柄中包含了对象实例数据和类型数据的地址信息\n 优点: 稳定,即使对象被移动,只改变句柄中的实例数据指针,reference不变\n \n{% qnimg object.jpg title:通过句柄访问对象图 alt:通过句柄访问对象图 extend:?imageView2/2/w/600 %}\n\n 2.直接指针方式:\n reference中存储的就是对象的地址\n 优点:速度快,节省一次指针定位的开销(HotSpot使用)\n \n{% qnimg 1bda289.jpg title:通过句柄访问对象图 alt:通过句柄访问对象图 extend:?imageView2/2/w/600 %}\n\n \n> ###### reference:存在栈中,用来操作堆中的具体对象,存储了对象的引用(句柄地址或直接地址)\n","source":"_posts/201703092017_object.md","raw":"---\ntitle: 对象在HotSpot虚拟机中\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription: 对象在内存的布局,访问定位\n\n---\n\n## 对象的建立位置\n### 对象在HotSpot虚拟机内存中存储分为三个区域\n 1.对象头(Hearder)\n 头中包含两部分信息:对象的运行时数据(官方叫Mark Word)以及类型指针;\n 2.实例数据(Instance Data)\n 3.对齐填充(Padding)\n\n> ###### 对象运行时数据有很多,但是对象头信息是与数据无关的额外信息,为了提高空间利用率,其数据结构不固定,会根据对象状态复用自身的存储空间\n> ###### **类型指针**是对象指向其类元数据的指针,虚拟机通过其确定对象是哪个类的实例(不是每个对象都有)\n> ###### 如果是数组,对象头中还有一块区域存放数组长度(一般对象可根据类型获得)\n\n> ###### 实例数据是真正的对象的有效信息,这些信息的顺序受到虚拟机分配策略参数的影响\n 默认分配策略: \n longs/doubles,ints,shorts/chars,bytes/booleans/oops(一般对象指针)\n 父类定义的变量在子类之前\n 相同宽度的字段总是被分配的一起\n\n\n\n## 对象的访问(方式)\n 1.句柄方式: \n 虚拟机在java堆中划分出一块叫句柄池,reference中存储对象的句柄地址,句柄中包含了对象实例数据和类型数据的地址信息\n 优点: 稳定,即使对象被移动,只改变句柄中的实例数据指针,reference不变\n \n{% qnimg object.jpg title:通过句柄访问对象图 alt:通过句柄访问对象图 extend:?imageView2/2/w/600 %}\n\n 2.直接指针方式:\n reference中存储的就是对象的地址\n 优点:速度快,节省一次指针定位的开销(HotSpot使用)\n \n{% qnimg 1bda289.jpg title:通过句柄访问对象图 alt:通过句柄访问对象图 extend:?imageView2/2/w/600 %}\n\n \n> ###### reference:存在栈中,用来操作堆中的具体对象,存储了对象的引用(句柄地址或直接地址)\n","slug":"201703092017_object","published":1,"date":"2020-01-15T05:50:39.597Z","updated":"2018-01-13T02:29:22.144Z","layout":"post","photos":[],"link":"","_id":"ckm3inup6000024uj6eipgv1y","content":"

对象的建立位置

对象在HotSpot虚拟机内存中存储分为三个区域

1.对象头(Hearder)\n    头中包含两部分信息:对象的运行时数据(官方叫Mark Word)以及类型指针;\n2.实例数据(Instance Data)\n3.对齐填充(Padding)\n
\n
对象运行时数据有很多,但是对象头信息是与数据无关的额外信息,为了提高空间利用率,其数据结构不固定,会根据对象状态复用自身的存储空间
类型指针是对象指向其类元数据的指针,虚拟机通过其确定对象是哪个类的实例(不是每个对象都有)
如果是数组,对象头中还有一块区域存放数组长度(一般对象可根据类型获得)
\n
\n
实例数据是真正的对象的有效信息,这些信息的顺序受到虚拟机分配策略参数的影响
默认分配策略: \n    longs/doubles,ints,shorts/chars,bytes/booleans/oops(一般对象指针)\n    父类定义的变量在子类之前\n    相同宽度的字段总是被分配的一起\n
\n\n

对象的访问(方式)

1.句柄方式:     \n    虚拟机在java堆中划分出一块叫句柄池,reference中存储对象的句柄地址,句柄中包含了对象实例数据和类型数据的地址信息\n    优点: 稳定,即使对象被移动,只改变句柄中的实例数据指针,reference不变\n
\"通过句柄访问对象图\"\n
2.直接指针方式:\n    reference中存储的就是对象的地址\n    优点:速度快,节省一次指针定位的开销(HotSpot使用)\n
\"通过句柄访问对象图\"\n
\n
reference:存在栈中,用来操作堆中的具体对象,存储了对象的引用(句柄地址或直接地址)
\n","site":{"data":{}},"excerpt":"

对象的建立位置

对象在HotSpot虚拟机内存中存储分为三个区域

1.对象头(Hearder)\n    头中包含两部分信息:对象的运行时数据(官方叫Mark Word)以及类型指针;\n2.实例数据(Instance Data)\n3.对齐填充(Padding)\n
\n
对象运行时数据有很多,但是对象头信息是与数据无关的额外信息,为了提高空间利用率,其数据结构不固定,会根据对象状态复用自身的存储空间
类型指针是对象指向其类元数据的指针,虚拟机通过其确定对象是哪个类的实例(不是每个对象都有)
如果是数组,对象头中还有一块区域存放数组长度(一般对象可根据类型获得)
\n
\n
实例数据是真正的对象的有效信息,这些信息的顺序受到虚拟机分配策略参数的影响
默认分配策略: \n    longs/doubles,ints,shorts/chars,bytes/booleans/oops(一般对象指针)\n    父类定义的变量在子类之前\n    相同宽度的字段总是被分配的一起\n
","more":"

对象的访问(方式)

1.句柄方式:     \n    虚拟机在java堆中划分出一块叫句柄池,reference中存储对象的句柄地址,句柄中包含了对象实例数据和类型数据的地址信息\n    优点: 稳定,即使对象被移动,只改变句柄中的实例数据指针,reference不变\n
\"通过句柄访问对象图\"\n
2.直接指针方式:\n    reference中存储的就是对象的地址\n    优点:速度快,节省一次指针定位的开销(HotSpot使用)\n
\"通过句柄访问对象图\"\n
\n
reference:存在栈中,用来操作堆中的具体对象,存储了对象的引用(句柄地址或直接地址)
"},{"title":"垃圾收集器与内存分配策略","comments":1,"description":null,"_content":"\n## 1. 垃圾收集 (Garbage Collection,GC)\n\nJava内存 运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随 线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个 栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器 进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问 题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一 样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也 可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配 和回收都是动态的,垃圾收集器所关注的是这部分内存\n\n## 2.判断对象是否存活算法\n 1. 引用计数算法 \n \n 给对象中添加一个引用计数器,每当有 一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;\n 任何时刻计数器为0 的对象就是不可能再被使用的。\n#### 优点:实现简单,判定效率很高 \n#### 缺点:很难解决对象 之间相互循环引用的问题。\n 2. 可达性分析算法 (在主流的商用程序语言(Java、C#...中实现)\n \n 这个算法的基本思 路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所 走过的路径称为引用链(Reference Chain),\n 当一个对象到GC Roots没有任何引用链相连 (用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的\n \n```text\n在Java语言中,可作为GC Roots的对象包括下面几种:\n 虚拟机栈(栈帧中的本地变量表)中引用的对象。 \n 方法区中类静态属性引用的对象。 \n 方法区中常量引用的对象。 \n 本地方法栈中JNI(即一般说的Native方法)引用的对\n```\n\n\n\n## 3.关于引用\n\n> 传统定义::如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块 内存代表着一个引用。\n\n> 当前的扩充定义: 引用分为4种\n```text\n 1. 强引用: 通过new出来的, 只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象\n 2. 软引用: 用来描述一些还有用但并非必需的对象\n 对于软引用关联着的对象,在系统将 要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回 收还没有足够的内存,才会抛出内存溢出异常。\n 在JDK 1.2之后,提供了SoftReference类来实 现软引用。 \n 3. 弱引用: 用来描述非必需的对象 更弱\n```\n \n\n\n","source":"_posts/201703101259_out_of_memory_error.md","raw":"---\ntitle: 垃圾收集器与内存分配策略\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription: \n\n---\n\n## 1. 垃圾收集 (Garbage Collection,GC)\n\nJava内存 运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随 线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个 栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器 进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问 题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一 样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也 可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配 和回收都是动态的,垃圾收集器所关注的是这部分内存\n\n## 2.判断对象是否存活算法\n 1. 引用计数算法 \n \n 给对象中添加一个引用计数器,每当有 一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;\n 任何时刻计数器为0 的对象就是不可能再被使用的。\n#### 优点:实现简单,判定效率很高 \n#### 缺点:很难解决对象 之间相互循环引用的问题。\n 2. 可达性分析算法 (在主流的商用程序语言(Java、C#...中实现)\n \n 这个算法的基本思 路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所 走过的路径称为引用链(Reference Chain),\n 当一个对象到GC Roots没有任何引用链相连 (用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的\n \n```text\n在Java语言中,可作为GC Roots的对象包括下面几种:\n 虚拟机栈(栈帧中的本地变量表)中引用的对象。 \n 方法区中类静态属性引用的对象。 \n 方法区中常量引用的对象。 \n 本地方法栈中JNI(即一般说的Native方法)引用的对\n```\n\n\n\n## 3.关于引用\n\n> 传统定义::如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块 内存代表着一个引用。\n\n> 当前的扩充定义: 引用分为4种\n```text\n 1. 强引用: 通过new出来的, 只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象\n 2. 软引用: 用来描述一些还有用但并非必需的对象\n 对于软引用关联着的对象,在系统将 要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回 收还没有足够的内存,才会抛出内存溢出异常。\n 在JDK 1.2之后,提供了SoftReference类来实 现软引用。 \n 3. 弱引用: 用来描述非必需的对象 更弱\n```\n \n\n\n","slug":"201703101259_out_of_memory_error","published":1,"date":"2020-01-15T05:50:39.600Z","updated":"2018-01-13T02:29:22.147Z","layout":"post","photos":[],"link":"","_id":"ckm3inuul000724uj3sunpke3","content":"

1. 垃圾收集 (Garbage Collection,GC)

Java内存 运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随 线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个 栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器 进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问 题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一 样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也 可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配 和回收都是动态的,垃圾收集器所关注的是这部分内存

\n

2.判断对象是否存活算法

1. 引用计数算法 \n\n给对象中添加一个引用计数器,每当有 一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;\n任何时刻计数器为0 的对象就是不可能再被使用的。\n

优点:实现简单,判定效率很高

缺点:很难解决对象 之间相互循环引用的问题。

2. 可达性分析算法  (在主流的商用程序语言(Java、C#...中实现)\n\n这个算法的基本思 路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所 走过的路径称为引用链(Reference Chain),\n当一个对象到GC Roots没有任何引用链相连 (用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的\n
1
2
3
4
5
在Java语言中,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对
\n\n

3.关于引用

\n

传统定义::如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块 内存代表着一个引用。

\n
\n
\n

当前的扩充定义: 引用分为4种

1
2
3
4
5
1. 强引用: 通过new出来的, 只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
2. 软引用: 用来描述一些还有用但并非必需的对象
对于软引用关联着的对象,在系统将 要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回 收还没有足够的内存,才会抛出内存溢出异常。
在JDK 1.2之后,提供了SoftReference类来实 现软引用。
3. 弱引用: 用来描述非必需的对象 更弱

\n
\n","site":{"data":{}},"excerpt":"

1. 垃圾收集 (Garbage Collection,GC)

Java内存 运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随 线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个 栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由JIT编译器 进行一些优化,但在本章基于概念模型的讨论中,大体上可以认为是编译期可知的),因此 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问 题,因为方法结束或者线程结束时,内存自然就跟随着回收了。而Java堆和方法区则不一 样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也 可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配 和回收都是动态的,垃圾收集器所关注的是这部分内存

\n

2.判断对象是否存活算法

1. 引用计数算法 \n\n给对象中添加一个引用计数器,每当有 一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;\n任何时刻计数器为0 的对象就是不可能再被使用的。\n

优点:实现简单,判定效率很高

缺点:很难解决对象 之间相互循环引用的问题。

2. 可达性分析算法  (在主流的商用程序语言(Java、C#...中实现)\n\n这个算法的基本思 路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所 走过的路径称为引用链(Reference Chain),\n当一个对象到GC Roots没有任何引用链相连 (用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的\n
1
2
3
4
5
在Java语言中,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对
","more":"

3.关于引用

\n

传统定义::如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块 内存代表着一个引用。

\n
\n
\n

当前的扩充定义: 引用分为4种

1
2
3
4
5
1. 强引用: 通过new出来的, 只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
2. 软引用: 用来描述一些还有用但并非必需的对象
对于软引用关联着的对象,在系统将 要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回 收还没有足够的内存,才会抛出内存溢出异常。
在JDK 1.2之后,提供了SoftReference类来实 现软引用。
3. 弱引用: 用来描述非必需的对象 更弱

\n
"},{"title":"使用iframe标签实现局部刷新","comments":1,"description":null,"_content":"\n### Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新,通过本文和大家一起学习iframe实现局部刷新的几种方法汇总,对iframe局部刷新相关知识感兴趣的朋友一起学习吧\n### Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新。\n### Iframe的用法与普通的标签元素DIV类似,可以指定在页面中嵌入的位置、颜色、界面布局等\n\n---\n\n> 一、iframe实现局部刷新方法一\n\n\n \n 1\n 2\n \n\n\n\n 当点a1时在iframe里显示a1.html的内容,点a2时在iframe里显示a2.html的内容\n \n\n\n--- \n \n> 二、iframe实现局部刷新的方法二\n\n\n 1\n 2\n \n\n\n备注: \n1.
同样也有target属性,作用和一样 这个方式如果提交到某个Action中再跳转到a1.html中效果一样,如果在Action中有req.set或session.set,最后在iframe中同样可以显示出来。\n \n2. 可以加入这些属性 去除iframe的默认样式\n\n\n frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" allowtransparency=\"yes\"\n\n\n---\n\n> 三:iframe实现局部刷新的方法三:\n\n\n \n\n\n\n1. 方案一:用iframe的name属性定位\n\n\n \n\n\n\n或\n\n \n\n\n2. 方案二:用iframe的id属性定位\n\n\n \n\n\n\n3. 方案三:当iframe的src为其它网站地址(跨域操作时)\n\n\n \n\n\n\n4. 方案四:通过和替换iframe的src来实现局部刷新\n可以用document.getElementById(\"iframname\").src=\"\"来进行iframe得重定向;\n示例代码如下:test.html\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
方格1方格2 方格3
\n \n \n \n \n \n
\n
\n
\n \n \n \n\n","source":"_posts/20170315_iframe.md","raw":"---\ntitle: 使用iframe标签实现局部刷新\n\ncomments: true \n\ntags: \n - HTML\n - iframe\n\ncategories: \n - 框架相关\n - 前端技术\n\ndescription: \n\n---\n\n### Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新,通过本文和大家一起学习iframe实现局部刷新的几种方法汇总,对iframe局部刷新相关知识感兴趣的朋友一起学习吧\n### Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新。\n### Iframe的用法与普通的标签元素DIV类似,可以指定在页面中嵌入的位置、颜色、界面布局等\n\n---\n\n> 一、iframe实现局部刷新方法一\n\n\n \n
1\n 2\n \n\n\n\n 当点a1时在iframe里显示a1.html的内容,点a2时在iframe里显示a2.html的内容\n \n\n\n--- \n \n> 二、iframe实现局部刷新的方法二\n\n\n 1\n 2\n \n\n\n备注: \n1. 同样也有target属性,作用和一样 这个方式如果提交到某个Action中再跳转到a1.html中效果一样,如果在Action中有req.set或session.set,最后在iframe中同样可以显示出来。\n \n2. 可以加入这些属性 去除iframe的默认样式\n\n\n frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" allowtransparency=\"yes\"\n\n\n---\n\n> 三:iframe实现局部刷新的方法三:\n\n\n \n\n\n\n1. 方案一:用iframe的name属性定位\n\n\n \n\n\n\n或\n\n \n\n\n2. 方案二:用iframe的id属性定位\n\n\n \n\n\n\n3. 方案三:当iframe的src为其它网站地址(跨域操作时)\n\n\n \n\n\n\n4. 方案四:通过和替换iframe的src来实现局部刷新\n可以用document.getElementById(\"iframname\").src=\"\"来进行iframe得重定向;\n示例代码如下:test.html\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
方格1方格2 方格3
\n \n \n \n \n \n
\n
\n
\n \n \n \n\n","slug":"20170315_iframe","published":1,"date":"2020-01-15T05:50:39.604Z","updated":"2018-01-13T02:29:22.151Z","layout":"post","photos":[],"link":"","_id":"ckm3inuun000924uj3izd6lo3","content":"

Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新,通过本文和大家一起学习iframe实现局部刷新的几种方法汇总,对iframe局部刷新相关知识感兴趣的朋友一起学习吧

Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新。

Iframe的用法与普通的标签元素DIV类似,可以指定在页面中嵌入的位置、颜色、界面布局等


\n
\n

一、iframe实现局部刷新方法一

\n
\n
<script type="text/javascript">\n $(function(){\n $("#a1").click(function(){\n  var name= $(this).attr("name");\n  $("#iframe").attr("src",name).ready();\n })\n $("#a2").click(function(){\n  var name= $(this).attr("name");\n  $("#iframe").attr("src",name).ready();\n })\n})\n</script>\n<a href="#" id="a1" name="a1.html">1</a>\n<a href="#" id="a2" name="a2.html">2</a>\n<iframe src="" id="iframe"></iframe> \n

当点a1时在iframe里显示a1.html的内容,点a2时在iframe里显示a2.html的内容

\n\n
\n
\n

二、iframe实现局部刷新的方法二

\n
\n
<a href="a1.html" id="a1" name="a1.html" target="i">1</a>\n<a href="a2.html" id="a2" name="a2.html" target="i">2</a>\n<iframe src="" id="iframe" name="i"></iframe> \n

备注:

\n
    \n
  1. 同样也有target属性,作用和一样 这个方式如果提交到某个Action中再跳转到a1.html中效果一样,如果在Action中有req.set或session.set,最后在iframe中同样可以显示出来。

  2. \n\n
  3. 可以加入这些属性 去除iframe的默认样式

    \n
  4. \n
\n
frameborder="no" border="0"  marginwidth="0" marginheight="0" scrolling="no" allowtransparency="yes"\n

\n
\n

三:iframe实现局部刷新的方法三:

\n
\n
<iframe src="1.htm" name="ifrmname" \nid="ifrmid"></iframe>\n
    \n
  1. 方案一:用iframe的name属性定位
  2. \n
\n
<input type="button" name="Button" value="Button" onclick="document.frames('ifrmname').location.reload()">\n

\n
<input type="button" name="Button" value="Button" onclick="document.all.ifrmname.document.location.reload()">\n
    \n
  1. 方案二:用iframe的id属性定位
  2. \n
\n
<input type="button" name="Button" value="Button" onclick="ifrmid.window.location.reload()">\n
    \n
  1. 方案三:当iframe的src为其它网站地址(跨域操作时)
  2. \n
\n
<input type="button" name="Button" value="Button" onclick="window.open(document.all.ifrmname.src,'ifrmname','')">\n
    \n
  1. 方案四:通过和替换iframe的src来实现局部刷新
    可以用document.getElementById(“iframname”).src=””来进行iframe得重定向;
    示例代码如下:test.html
  2. \n
\n
<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml">\n<head>\n <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n <title></title>\n <script type="text/javascript">\n  function partRefresh() {\n   document.getElementById("iframe1Id").src = "a2.html"; // 方法一: 通过和替换iframe的src来实现局部刷新\n  }\n </script>\n </head>\n <body>\n  <table border="1" width="90%" align="center">\n   <tr\n    style="background: #F0F0E4"><td>方格1</td><td>方格2</td> <td>方格3</td>\n   </tr>\n   <tr>\n    <td>\n     <iframe src="a1.html" id="iframe1Id" name="iframe1Name" width="100%"></iframe>\n    </td>\n    <td>\n     <iframe src="a2.html" id="iframe2Id" name="iframe2Name" width="100%"></iframe>\n    </td>\n    <td>\n     <iframe src="a3.html" id="iframe3Id" name="iframe3Name" width="100%"></iframe>\n    </td>\n   </tr>\n  </table>\n  <br>\n  <br>\n  <input type="button" value="IFRAME局部刷新" style="margin-left: 70px;" onclick="partRefresh();">\n </body>\n</html>\n
","site":{"data":{}},"excerpt":"

Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新,通过本文和大家一起学习iframe实现局部刷新的几种方法汇总,对iframe局部刷新相关知识感兴趣的朋友一起学习吧

Iframe是一种嵌入网页的框架形式,Web页面可以通过更改嵌入的部分,达到部分内容刷新。

Iframe的用法与普通的标签元素DIV类似,可以指定在页面中嵌入的位置、颜色、界面布局等


\n
\n

一、iframe实现局部刷新方法一

\n
\n
<script type="text/javascript">\n $(function(){\n $("#a1").click(function(){\n  var name= $(this).attr("name");\n  $("#iframe").attr("src",name).ready();\n })\n $("#a2").click(function(){\n  var name= $(this).attr("name");\n  $("#iframe").attr("src",name).ready();\n })\n})\n</script>\n<a href="#" id="a1" name="a1.html">1</a>\n<a href="#" id="a2" name="a2.html">2</a>\n<iframe src="" id="iframe"></iframe> \n

当点a1时在iframe里显示a1.html的内容,点a2时在iframe里显示a2.html的内容

","more":"
\n
\n

二、iframe实现局部刷新的方法二

\n
\n
<a href="a1.html" id="a1" name="a1.html" target="i">1</a>\n<a href="a2.html" id="a2" name="a2.html" target="i">2</a>\n<iframe src="" id="iframe" name="i"></iframe> \n

备注:

\n
    \n
  1. 同样也有target属性,作用和一样 这个方式如果提交到某个Action中再跳转到a1.html中效果一样,如果在Action中有req.set或session.set,最后在iframe中同样可以显示出来。

    \n
  2. \n
  3. 可以加入这些属性 去除iframe的默认样式

    \n
  4. \n
\n
frameborder="no" border="0"  marginwidth="0" marginheight="0" scrolling="no" allowtransparency="yes"\n

\n
\n

三:iframe实现局部刷新的方法三:

\n
\n
<iframe src="1.htm" name="ifrmname" \nid="ifrmid"></iframe>\n
    \n
  1. 方案一:用iframe的name属性定位
  2. \n
\n
<input type="button" name="Button" value="Button" onclick="document.frames('ifrmname').location.reload()">\n

\n
<input type="button" name="Button" value="Button" onclick="document.all.ifrmname.document.location.reload()">\n
    \n
  1. 方案二:用iframe的id属性定位
  2. \n
\n
<input type="button" name="Button" value="Button" onclick="ifrmid.window.location.reload()">\n
    \n
  1. 方案三:当iframe的src为其它网站地址(跨域操作时)
  2. \n
\n
<input type="button" name="Button" value="Button" onclick="window.open(document.all.ifrmname.src,'ifrmname','')">\n
    \n
  1. 方案四:通过和替换iframe的src来实现局部刷新
    可以用document.getElementById(“iframname”).src=””来进行iframe得重定向;
    示例代码如下:test.html
  2. \n
\n
<!DOCTYPE html>\n<html xmlns="http://www.w3.org/1999/xhtml">\n<head>\n <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n <title></title>\n <script type="text/javascript">\n  function partRefresh() {\n   document.getElementById("iframe1Id").src = "a2.html"; // 方法一: 通过和替换iframe的src来实现局部刷新\n  }\n </script>\n </head>\n <body>\n  <table border="1" width="90%" align="center">\n   <tr\n    style="background: #F0F0E4"><td>方格1</td><td>方格2</td> <td>方格3</td>\n   </tr>\n   <tr>\n    <td>\n     <iframe src="a1.html" id="iframe1Id" name="iframe1Name" width="100%"></iframe>\n    </td>\n    <td>\n     <iframe src="a2.html" id="iframe2Id" name="iframe2Name" width="100%"></iframe>\n    </td>\n    <td>\n     <iframe src="a3.html" id="iframe3Id" name="iframe3Name" width="100%"></iframe>\n    </td>\n   </tr>\n  </table>\n  <br>\n  <br>\n  <input type="button" value="IFRAME局部刷新" style="margin-left: 70px;" onclick="partRefresh();">\n </body>\n</html>\n
"},{"title":"maven安装与命令","comments":1,"description":null,"_content":"\n\n## 下载安装\n 1.下载 地址 : http://maven.apache.org/download.cgi\n 2.解压\n 3.配置环境变量: \n 1.添加 M2_HOME : 解压目录\n 2.修改 path : 添加maven的bin目录\n 3.测试 命令行窗口输入: mvn -v\n\n\n\n## 目录结构\n{% qnimg ml.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n```text\n - src : 源代码目录\n -main:\n - java : java源码\n - resources: 资源文件目录,配置文件\n - webapp : 页面代码 jsp\n - WEB-INF\n - web.xml\n - test:\n - target 编译输出目录\n - pom.xml pom文件\n```\n\n```xml\n\n \n 4.0.0 maven版本\n org.living 组织名 一般写包名\n FSCLassCloud 模块名 一般写项目名\n 1.0-SNAPSHOT 版本号 SNAPSHOT:快照仓库(快照) alpha(内测版) beta(公测版) release发布仓库(稳定版) GA(正式发布)\n war 打包方式 默认jar ,还有war,zip,pom\n AAA 项目描述名\n http://maven.apache.org 项目地址\n 项目描述\n \n 加入的依赖(jar包) groupId,artifactId,version三个属性组成依赖的坐标\n \n mysql \n mysql-connector-java\n ${mysql.jdbc.version} \n compile 依赖范围\n false 设置依赖是否可选(继承)\n 排除依赖传递列表(里面写依赖的坐标)\n \n \n \n \n \n \n \n \n 插件列表\n 加载的插件\n org.apache.maven.plugins\n maven-compiler-plugin\n 3.1\n \n ${jdk.version}\n ${jdk.version}\n \n \n \n \n \n 继承的父模块\n 聚合多个模块\n \n \n```\n\n配置文件: maven目录/conf/settings.xml 里面包含了很多maven的仓库的配置,可以进行自定义\n \n*仓库: 本地仓库(电脑本地仓库地址) 远程仓库(远程服务器的仓库) 镜像仓库(国外仓库无法访问,国内创建的远程仓库)*\n\n```xml\n \n 1. E:/MAVEN-3.3.9-bin/repo 修改本地仓库位置(默认在c盘)\n \n 2. 修改远程仓库地址为镜像地址(如果中央仓库访问很慢就换把)\n \n UK \n UK Central \n http://uk.maven.org/maven2 \n central \n \n \n \n 3. 修改jdk版本支持\n jdk-1.8\n \n true\n 1.8\n \n \n 1.8\n 1.8\n 1.8\n \t \n \n\n```\n \n\n## 命令\n>1. mvn -v 查看maven版本信息\n>2. mvn compile 编译(第一次编译会下载插件和jar包)\n>3. mvn test 运行测试\n>4. mvn package 打包\n>5. mvn clean 清理,删除编译后的文件--target目录\n>6. mvn install 安装jar包到本地仓库\n>6. mvn archetype:generate 自动构建maven目录\n\n注: 手动下载的jar包安装到本地仓库方式\n>mvn install:install-file -Dfile=jar包的位置 -DgroupId=jar包项目的groupId -DartifactId=jar包项目的artifactId -Dversion=jar包项目的version -Dpackaging=jar\n\n注:自动构建maven目录写法\n>mvn archetype:generate -DgroupId=组织名 -DartifactId=项目名 -Dversion=版本 -Dpackaging=包名\n\n## classpath 有三种 编译 测试 运行\n## 依赖的范围 \n 1.compile (默认) 编译测试运行都有效\n 2.test 只是在测试范围有效\n 3.provided 编译测试时候有效 运行时(jar)不会被加入进去\n 4.runtime 测试和运行时有效\n 5.system 编译测试有效 移植性差 与本地系统有关\n 6.import 导入的范围 只使用在dependencyManagement中 表示是从其他的pom中继承的依赖\n \n### 面对依赖冲突的原则 \n 1.短路优先 当依赖了同一个项目的不同版本 则哪个最近就解析哪个\n 2.路径相同则先声明先优先 先解析谁\n \n### 聚合: 几个项目(或模块)放到一起运行 \n\n### 继承: 继承父类项目pom中的配置 ","source":"_posts/201703101526_maven.md","raw":"---\ntitle: maven安装与命令\n\ncomments: true \n\ntags: \n - Maven\n\ncategories: \n - 程序安装与配置\n\ndescription:\n\n---\n\n\n## 下载安装\n 1.下载 地址 : http://maven.apache.org/download.cgi\n 2.解压\n 3.配置环境变量: \n 1.添加 M2_HOME : 解压目录\n 2.修改 path : 添加maven的bin目录\n 3.测试 命令行窗口输入: mvn -v\n\n\n\n## 目录结构\n{% qnimg ml.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n```text\n - src : 源代码目录\n -main:\n - java : java源码\n - resources: 资源文件目录,配置文件\n - webapp : 页面代码 jsp\n - WEB-INF\n - web.xml\n - test:\n - target 编译输出目录\n - pom.xml pom文件\n```\n\n```xml\n\n \n 4.0.0 maven版本\n org.living 组织名 一般写包名\n FSCLassCloud 模块名 一般写项目名\n 1.0-SNAPSHOT 版本号 SNAPSHOT:快照仓库(快照) alpha(内测版) beta(公测版) release发布仓库(稳定版) GA(正式发布)\n war 打包方式 默认jar ,还有war,zip,pom\n AAA 项目描述名\n http://maven.apache.org 项目地址\n 项目描述\n \n 加入的依赖(jar包) groupId,artifactId,version三个属性组成依赖的坐标\n \n mysql \n mysql-connector-java\n ${mysql.jdbc.version} \n compile 依赖范围\n false 设置依赖是否可选(继承)\n 排除依赖传递列表(里面写依赖的坐标)\n \n \n \n \n \n \n \n \n 插件列表\n 加载的插件\n org.apache.maven.plugins\n maven-compiler-plugin\n 3.1\n \n ${jdk.version}\n ${jdk.version}\n \n \n \n \n \n 继承的父模块\n 聚合多个模块\n \n \n```\n\n配置文件: maven目录/conf/settings.xml 里面包含了很多maven的仓库的配置,可以进行自定义\n \n*仓库: 本地仓库(电脑本地仓库地址) 远程仓库(远程服务器的仓库) 镜像仓库(国外仓库无法访问,国内创建的远程仓库)*\n\n```xml\n \n 1. E:/MAVEN-3.3.9-bin/repo 修改本地仓库位置(默认在c盘)\n \n 2. 修改远程仓库地址为镜像地址(如果中央仓库访问很慢就换把)\n \n UK \n UK Central \n http://uk.maven.org/maven2 \n central \n \n \n \n 3. 修改jdk版本支持\n jdk-1.8\n \n true\n 1.8\n \n \n 1.8\n 1.8\n 1.8\n \t \n \n\n```\n \n\n## 命令\n>1. mvn -v 查看maven版本信息\n>2. mvn compile 编译(第一次编译会下载插件和jar包)\n>3. mvn test 运行测试\n>4. mvn package 打包\n>5. mvn clean 清理,删除编译后的文件--target目录\n>6. mvn install 安装jar包到本地仓库\n>6. mvn archetype:generate 自动构建maven目录\n\n注: 手动下载的jar包安装到本地仓库方式\n>mvn install:install-file -Dfile=jar包的位置 -DgroupId=jar包项目的groupId -DartifactId=jar包项目的artifactId -Dversion=jar包项目的version -Dpackaging=jar\n\n注:自动构建maven目录写法\n>mvn archetype:generate -DgroupId=组织名 -DartifactId=项目名 -Dversion=版本 -Dpackaging=包名\n\n## classpath 有三种 编译 测试 运行\n## 依赖的范围 \n 1.compile (默认) 编译测试运行都有效\n 2.test 只是在测试范围有效\n 3.provided 编译测试时候有效 运行时(jar)不会被加入进去\n 4.runtime 测试和运行时有效\n 5.system 编译测试有效 移植性差 与本地系统有关\n 6.import 导入的范围 只使用在dependencyManagement中 表示是从其他的pom中继承的依赖\n \n### 面对依赖冲突的原则 \n 1.短路优先 当依赖了同一个项目的不同版本 则哪个最近就解析哪个\n 2.路径相同则先声明先优先 先解析谁\n \n### 聚合: 几个项目(或模块)放到一起运行 \n\n### 继承: 继承父类项目pom中的配置 ","slug":"201703101526_maven","published":1,"date":"2020-01-15T05:50:39.603Z","updated":"2018-01-13T02:29:22.149Z","layout":"post","photos":[],"link":"","_id":"ckm3inuuq000c24uj0phmqrm5","content":"

下载安装

1.下载 地址 : http://maven.apache.org/download.cgi\n2.解压\n3.配置环境变量: \n    1.添加 M2_HOME : 解压目录\n    2.修改 path : 添加maven的bin目录\n    3.测试 命令行窗口输入: mvn -v\n
\n

目录结构

\"图片说明\"\n
1
2
3
4
5
6
7
8
9
10
- src :                             源代码目录
-main:
- java : java源码
- resources: 资源文件目录,配置文件
- webapp : 页面代码 jsp
- WEB-INF
- web.xml
- test:
- target 编译输出目录
- pom.xml pom文件
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

<!--pom文件-->
<modelVersion>4.0.0</modelVersion> maven版本
<groupId>org.living</groupId> 组织名 一般写包名
<artifactId>FSCLassCloud</artifactId> 模块名 一般写项目名
<version>1.0-SNAPSHOT</version> 版本号 SNAPSHOT:快照仓库(快照) alpha(内测版) beta(公测版) release发布仓库(稳定版) GA(正式发布)
<packaging>war</packaging> 打包方式 默认jar ,还有war,zip,pom
<name>AAA</name> 项目描述名
<url>http://maven.apache.org</url> 项目地址
<description></description> 项目描述

<dependencies> 加入的依赖(jar包) groupId,artifactId,version三个属性组成依赖的坐标
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.jdbc.version}</version>
<scope>compile</scope> 依赖范围
<optional>false</optional> 设置依赖是否可选(继承)
<exclusions> 排除依赖传递列表(里面写依赖的坐标)
<exclusion>

</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins> 插件列表
<plugin> 加载的插件
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
</plugins>
</build>

<parent></parent> 继承的父模块
<modules> 聚合多个模块
<module></module>
</modules>
\n

配置文件: maven目录/conf/settings.xml 里面包含了很多maven的仓库的配置,可以进行自定义

\n

仓库: 本地仓库(电脑本地仓库地址) 远程仓库(远程服务器的仓库) 镜像仓库(国外仓库无法访问,国内创建的远程仓库)

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--修改settings.xml文件:-->
1. <localRepository>E:/MAVEN-3.3.9-bin/repo</localRepository> 修改本地仓库位置(默认在c盘)

2. <mirrors> 修改远程仓库地址为镜像地址(如果中央仓库访问很慢就换把)
<mirror>
<id>UK</id>
<name>UK Central</name>
<url>http://uk.maven.org/maven2</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>

3. <profile> 修改jdk版本支持
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
\t </properties>
</profile>
\n

命令

\n
    \n
  1. mvn -v 查看maven版本信息
  2. \n
  3. mvn compile 编译(第一次编译会下载插件和jar包)
  4. \n
  5. mvn test 运行测试
  6. \n
  7. mvn package 打包
  8. \n
  9. mvn clean 清理,删除编译后的文件–target目录
  10. \n
  11. mvn install 安装jar包到本地仓库
  12. \n
  13. mvn archetype:generate 自动构建maven目录
  14. \n
\n
\n

注: 手动下载的jar包安装到本地仓库方式

\n
\n

mvn install:install-file -Dfile=jar包的位置 -DgroupId=jar包项目的groupId -DartifactId=jar包项目的artifactId -Dversion=jar包项目的version -Dpackaging=jar

\n
\n

注:自动构建maven目录写法

\n
\n

mvn archetype:generate -DgroupId=组织名 -DartifactId=项目名 -Dversion=版本 -Dpackaging=包名

\n
\n

classpath 有三种 编译 测试 运行

依赖的范围

1.compile (默认) 编译测试运行都有效\n2.test      只是在测试范围有效\n3.provided  编译测试时候有效 运行时(jar)不会被加入进去\n4.runtime   测试和运行时有效\n5.system    编译测试有效 移植性差 与本地系统有关\n6.import    导入的范围 只使用在dependencyManagement中 表示是从其他的pom中继承的依赖\n

面对依赖冲突的原则

1.短路优先 当依赖了同一个项目的不同版本 则哪个最近就解析哪个\n2.路径相同则先声明先优先 先解析谁\n

聚合: 几个项目(或模块)放到一起运行

继承: 继承父类项目pom中的配置

","site":{"data":{}},"excerpt":"

下载安装

1.下载 地址 : http://maven.apache.org/download.cgi\n2.解压\n3.配置环境变量: \n    1.添加 M2_HOME : 解压目录\n    2.修改 path : 添加maven的bin目录\n    3.测试 命令行窗口输入: mvn -v\n
","more":"

目录结构

\"图片说明\"\n
1
2
3
4
5
6
7
8
9
10
- src :                             源代码目录
-main:
- java : java源码
- resources: 资源文件目录,配置文件
- webapp : 页面代码 jsp
- WEB-INF
- web.xml
- test:
- target 编译输出目录
- pom.xml pom文件
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

<!--pom文件-->
<modelVersion>4.0.0</modelVersion> maven版本
<groupId>org.living</groupId> 组织名 一般写包名
<artifactId>FSCLassCloud</artifactId> 模块名 一般写项目名
<version>1.0-SNAPSHOT</version> 版本号 SNAPSHOT:快照仓库(快照) alpha(内测版) beta(公测版) release发布仓库(稳定版) GA(正式发布)
<packaging>war</packaging> 打包方式 默认jar ,还有war,zip,pom
<name>AAA</name> 项目描述名
<url>http://maven.apache.org</url> 项目地址
<description></description> 项目描述

<dependencies> 加入的依赖(jar包) groupId,artifactId,version三个属性组成依赖的坐标
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.jdbc.version}</version>
<scope>compile</scope> 依赖范围
<optional>false</optional> 设置依赖是否可选(继承)
<exclusions> 排除依赖传递列表(里面写依赖的坐标)
<exclusion>

</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins> 插件列表
<plugin> 加载的插件
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
</plugins>
</build>

<parent></parent> 继承的父模块
<modules> 聚合多个模块
<module></module>
</modules>
\n

配置文件: maven目录/conf/settings.xml 里面包含了很多maven的仓库的配置,可以进行自定义

\n

仓库: 本地仓库(电脑本地仓库地址) 远程仓库(远程服务器的仓库) 镜像仓库(国外仓库无法访问,国内创建的远程仓库)

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--修改settings.xml文件:-->
1. <localRepository>E:/MAVEN-3.3.9-bin/repo</localRepository> 修改本地仓库位置(默认在c盘)

2. <mirrors> 修改远程仓库地址为镜像地址(如果中央仓库访问很慢就换把)
<mirror>
<id>UK</id>
<name>UK Central</name>
<url>http://uk.maven.org/maven2</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>

3. <profile> 修改jdk版本支持
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
\t </properties>
</profile>
\n

命令

\n
    \n
  1. mvn -v 查看maven版本信息
  2. \n
  3. mvn compile 编译(第一次编译会下载插件和jar包)
  4. \n
  5. mvn test 运行测试
  6. \n
  7. mvn package 打包
  8. \n
  9. mvn clean 清理,删除编译后的文件–target目录
  10. \n
  11. mvn install 安装jar包到本地仓库
  12. \n
  13. mvn archetype:generate 自动构建maven目录
  14. \n
\n
\n

注: 手动下载的jar包安装到本地仓库方式

\n
\n

mvn install:install-file -Dfile=jar包的位置 -DgroupId=jar包项目的groupId -DartifactId=jar包项目的artifactId -Dversion=jar包项目的version -Dpackaging=jar

\n
\n

注:自动构建maven目录写法

\n
\n

mvn archetype:generate -DgroupId=组织名 -DartifactId=项目名 -Dversion=版本 -Dpackaging=包名

\n
\n

classpath 有三种 编译 测试 运行

依赖的范围

1.compile (默认) 编译测试运行都有效\n2.test      只是在测试范围有效\n3.provided  编译测试时候有效 运行时(jar)不会被加入进去\n4.runtime   测试和运行时有效\n5.system    编译测试有效 移植性差 与本地系统有关\n6.import    导入的范围 只使用在dependencyManagement中 表示是从其他的pom中继承的依赖\n

面对依赖冲突的原则

1.短路优先 当依赖了同一个项目的不同版本 则哪个最近就解析哪个\n2.路径相同则先声明先优先 先解析谁\n

聚合: 几个项目(或模块)放到一起运行

继承: 继承父类项目pom中的配置

"},{"title":"Hexo博客插件安装","comments":1,"description":null,"toc":true,"mathjax":true,"_content":"\n\n{% qnimg hexo.png title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n\n\n\n### Hexo添加网易云跟贴(已关闭): https://www.levey.cn/2017/415.html\n\n\n### Hexo添加七牛插件,自动上传静态文件: https://github.com/gyk001/hexo-qiniu-sync\n\n\n### Hexo添加静态文件的CDN加速(可配置七牛): https://github.com/zqjimlove/hexo-cdnify\n\n### 主题介绍: https://www.haomwei.com/technology/maupassant-hexo.html\n\n```text\n数学公式\n\n要启用数学公式支持,请在Hexo目录的_config.yml中添加:\n\n mathjax: true\n\n并在相应文章的front-matter中添加mathjax: true,例如:\n\n title: Test Math\n date: 2016-04-05 14:16:00\n categories: math\n mathjax: true\n\n数学公式的默认定界符是$$...$$和\\\\[...\\\\](对于块级公式),以及$...$和\\\\(...\\\\)(对于行内公式)。\n\n但是,如果你的文章内容中经常出现美元符号“$”, 或者说你想将“$”用作美元符号而非行内公式的定界符,请在Hexo目录的_config.yml中添加:\n\n mathjax2: true\n\n而不是mathjax: true。 相应地,在需要使用数学公式的文章的front-matter中也添加mathjax2: true。\n```","source":"_posts/20170315_test.md","raw":"---\ntitle: Hexo博客插件安装\n\ncomments: true \n\ntags: Hexo优化\n\ncategories: \n\ndescription: \n\ntoc: true\n\nmathjax: true\n\n---\n\n\n{% qnimg hexo.png title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n\n\n\n### Hexo添加网易云跟贴(已关闭): https://www.levey.cn/2017/415.html\n\n\n### Hexo添加七牛插件,自动上传静态文件: https://github.com/gyk001/hexo-qiniu-sync\n\n\n### Hexo添加静态文件的CDN加速(可配置七牛): https://github.com/zqjimlove/hexo-cdnify\n\n### 主题介绍: https://www.haomwei.com/technology/maupassant-hexo.html\n\n```text\n数学公式\n\n要启用数学公式支持,请在Hexo目录的_config.yml中添加:\n\n mathjax: true\n\n并在相应文章的front-matter中添加mathjax: true,例如:\n\n title: Test Math\n date: 2016-04-05 14:16:00\n categories: math\n mathjax: true\n\n数学公式的默认定界符是$$...$$和\\\\[...\\\\](对于块级公式),以及$...$和\\\\(...\\\\)(对于行内公式)。\n\n但是,如果你的文章内容中经常出现美元符号“$”, 或者说你想将“$”用作美元符号而非行内公式的定界符,请在Hexo目录的_config.yml中添加:\n\n mathjax2: true\n\n而不是mathjax: true。 相应地,在需要使用数学公式的文章的front-matter中也添加mathjax2: true。\n```","slug":"20170315_test","published":1,"date":"2020-01-15T05:50:39.605Z","updated":"2018-01-13T02:29:22.152Z","layout":"post","photos":[],"link":"","_id":"ckm3inuus000g24ujotrndkrx","content":"\"图片说明\"\n\n

Hexo添加网易云跟贴(已关闭): https://www.levey.cn/2017/415.html

Hexo添加七牛插件,自动上传静态文件: https://github.com/gyk001/hexo-qiniu-sync

Hexo添加静态文件的CDN加速(可配置七牛): https://github.com/zqjimlove/hexo-cdnify

主题介绍: https://www.haomwei.com/technology/maupassant-hexo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
数学公式

要启用数学公式支持,请在Hexo目录的_config.yml中添加:

mathjax: true

并在相应文章的front-matter中添加mathjax: true,例如:

title: Test Math
date: 2016-04-05 14:16:00
categories: math
mathjax: true

数学公式的默认定界符是$$...$$和\\\\[...\\\\](对于块级公式),以及$...$和\\\\(...\\\\)(对于行内公式)。

但是,如果你的文章内容中经常出现美元符号“$”, 或者说你想将“$”用作美元符号而非行内公式的定界符,请在Hexo目录的_config.yml中添加:

mathjax2: true

而不是mathjax: true。 相应地,在需要使用数学公式的文章的front-matter中也添加mathjax2: true。
","site":{"data":{}},"excerpt":"\"图片说明\"","more":"

Hexo添加网易云跟贴(已关闭): https://www.levey.cn/2017/415.html

Hexo添加七牛插件,自动上传静态文件: https://github.com/gyk001/hexo-qiniu-sync

Hexo添加静态文件的CDN加速(可配置七牛): https://github.com/zqjimlove/hexo-cdnify

主题介绍: https://www.haomwei.com/technology/maupassant-hexo.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
数学公式

要启用数学公式支持,请在Hexo目录的_config.yml中添加:

mathjax: true

并在相应文章的front-matter中添加mathjax: true,例如:

title: Test Math
date: 2016-04-05 14:16:00
categories: math
mathjax: true

数学公式的默认定界符是$$...$$和\\\\[...\\\\](对于块级公式),以及$...$和\\\\(...\\\\)(对于行内公式)。

但是,如果你的文章内容中经常出现美元符号“$”, 或者说你想将“$”用作美元符号而非行内公式的定界符,请在Hexo目录的_config.yml中添加:

mathjax2: true

而不是mathjax: true。 相应地,在需要使用数学公式的文章的front-matter中也添加mathjax2: true。
"},{"title":"linux(centos)下使用jenkins + maven + git码云(或github) 实现自动化构建项目","comments":1,"description":"jenkins的安装,使用,自动从码云获取项目并构建项目","_content":"\n###1.下载jenkins 官网下载地址,自己选取对应系统 https://jenkins.io/download/\n\n> 下载rpm或者直接下载war包 https://pkg.jenkins.io/redhat/jenkins-2.9-1.1.noarch.rpm\n\n 系统环境要求: jdk, tomcat, git, maven (没有就安装先)\n\n> rpm -ivh jenkins-2.9-1.1.noarch.rpm 我的安装到 /usr/lib/jenkins目录\n\n Jenkins主目录是用户目录下的.jenkins目录 /root/.jenkins\n \n\n\n> 复制文件夹内的jenkins.war到tomcat目录下的webapps目录下 并启动tomcat (./startup.sh)\n\n> 进入: cd ~/.jenkins/ \n\n 目录介绍:\n jobs: 项目配置目录(新建的job,即一个个项目 的配置存放在这里)\n logs: 日志目录\n workspaces: 默认的工作空间(每个项目会存放在这里)\n users: 创建的用户目录\n secrets: 初始的设置(包括初始化的密码, 在创建了用户以后会自动删除里面的初始化密码文件)\n\n\n> tomcat启动以后 访问网页 http://127.0.0.1/jenkins 提示输入初始化密码, 密码在secrets目录下的initialAdminPassword文件内\n> 进入后先创建一个用户(以后就用这个用户登录), 然后提示下载插件(可以下载推荐的也可以自定义)\n\n> ##按照提示新建一个项目 如图:这里测试从码云获取项目,so选择第一个, 设置完后 保存\n\n(如果是maven项目 需要下载个maven插件-> Maven Integration plugin 在设置里下载 安装重启 下面介绍)\n\n{% qnimg jenkins.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n 填写内容:\n 项目名称: 最好和真正的项目名相同\n 描述: 随意\n \n 源码管理: 选择 git (当遇到 401, 没权限, 读取远程key失败请看下面错误解决)\n 构建触发器: 设置什么时候触发构建功能(可以用脚本,或者有人提交代码到码云时候构建,或者定时构建都可以)\n \n (jenkins有各种提示, 都介绍的很详细了) \n \n \n{% qnimg jenkins_a.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n{% qnimg jenkins_b.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n*如果项目是私有的项目(如码云中的), 则需要设置用户名密码--在 Credentials 中配置 Add 一个*\n{% qnimg jenkins_c.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n{% qnimg jenkins_maven.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n> 点击左侧 立即构建\n> 到主面板查看项目构建情况: 红色代表失败, 蓝色表示成功\n\n*可以再面板的 系统管理/系统设置 中修改设置*\n\n\n 系统设置:\n 1.点击高级设置: 可以修改 工作空间根目录,构建记录根目录\n 2.可以修改时间格式\n 3.可以修改ssh端口,管理员邮件地址,Jenkins URL等等\n Global Tool Configuration:\n 1.设置maven路径\n 2.设置jdk路径\n 3.设置git文件路径\n \n\n> ##对于maven项目 配置如图一,可自行更改\n{% qnimg jenkins_maven2.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n下图是点击构建以后打印的日志信息(正在下载依赖) (but有个问题 下载的jar包并没有按照我maven中配置的仓库地址,而是下载到了.jenkins目录下)\n{% qnimg jenkins_maven.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n\n> ##发布war到tomcat: 由于这里编译就和自己电脑打包项目一样,会把war包放在target目录下, 我们需要一个插件把其发送到tomcat的webapps中\n\n> (下载插件 Deploy to container Plugin)\n\n>安装插件以后进入项目的配置界面 设置\"构建后操作\",里面就会多一个 \"Deploy war/ear to a container\" 选项\n\n\n 配置:\n WAR/EAR files: target/*.war (war包的相对路径,相对工作空间)\n Context path: MeetCode (发布到tomcat的路径,这里我用项目名,到时候访问就用http://127.0.0.1:80/MeetCode)\n \n Containers: 配置一个tomcat容器(我用的tomcat8,但是我的版本没有 只能用7x)\n Manager user name: (tomcat用户名: 进入tomcat/conf/tomcat-users.xml设置)\n Manager password: 密码\n Tomcat URL: http://127.0.0.1:80 (设置远程tomcat访问路径)\n \n \n\n>设置完毕 保存 构建 可看日志观察构建过程 (进入tomcat/webapps下 发现war包正在传入), success之后访问网页可用\n\n> ## 其实也可以不发送war包, 可以在Post Steps中配置maven运行命令*\n\n 要求: 1. 在pom文件中加入tomcat插件\n 2. 如下图配置,则构建最后会直接通过maven运行启动项目\n \n 其他问题: 运行其实依然处于正在构建状态(可以自己查看项目构建状态),\n 所以需要在项目配置中勾选\"丢弃旧的构建\",保持构建的最大个数填写\"1\",(也可以设置构建最大时间);\n\n{% qnimg blog_jenkins.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n\n> ##设置邮件消息\n 1.进入 系统管理 / 系统设置 / 邮件通知\n \n 设置选项: (这里用qq邮箱测试)\n SMTP服务器: smtp.qq.com\n 用户默认邮件后缀: @qq.com\n 使用SMTP认证: true\n \t 用户名: (作为发件箱的qq邮箱,可以带或不带后缀)\n \t 密码: (一般是邮箱密码,qq邮箱特殊,需要进入qq邮箱/设置/账户设置, 发送短信生成一个smtp密码)\n \t 使用SSL协议: false\n \t SMTP端口: 587\n \t Reply-To Address: 我没写\n \t 字符集: UTF-8\n \t \n \t 测试: 写另一个邮箱地址进行测试\n 注意: 如果失败, 提示com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be sam\n 需要设置\t系统管理员邮件地址, 在本页面上面,必须与发件箱一致\n \n---\n\n## 错误解决:\n> 问题: 设置git地址时候 遇到401错误 或者 fatal: Could not read from remote repository\n\n> 原因: 本地git没有配置SSH公钥 \n\n* 这个配置在github中在右上角 Settings/SSH keys and GPG keys 中\n* 在码云中在右上角 修改资料/SSH公钥 中(二者命令不同)\n\n\n\n github生成公钥 命令:\n git config --global user.name \"XXXXX\"\n git config --global user.email \"XXXXXXX@gmail.com\"\n 1.查看是否已经有了ssh密钥:cd ~/.ssh\n 如果没有密钥则不会有此文件夹,有则备份删除\n 2.生存密钥:\n ssh-keygen -t rsa -C \"XXXXXXX@gmail.com\"\n 按3个回车,密码为空。\n 此时生成两个文件:id_rsa和id_rsa.pub 公钥在id_rsa.pub中,查看并复制粘贴到上面github的配公钥的地方,添加一个公钥\n 并且在本地也添加公钥\n ssh-add id_rsa.pub\n \n 码云生成公钥 命令: 见 http://git.mydoc.io/?t=154712\n \n","source":"_posts/20170504_jenkins.md","raw":"---\ntitle: linux(centos)下使用jenkins + maven + git码云(或github) 实现自动化构建项目\n\ncomments: true\n\ntags: \n - jenkins\n - Git\n - maven\n\ncategories: \n - Git\n\ndescription: jenkins的安装,使用,自动从码云获取项目并构建项目\n\n---\n\n###1.下载jenkins 官网下载地址,自己选取对应系统 https://jenkins.io/download/\n\n> 下载rpm或者直接下载war包 https://pkg.jenkins.io/redhat/jenkins-2.9-1.1.noarch.rpm\n\n 系统环境要求: jdk, tomcat, git, maven (没有就安装先)\n\n> rpm -ivh jenkins-2.9-1.1.noarch.rpm 我的安装到 /usr/lib/jenkins目录\n\n Jenkins主目录是用户目录下的.jenkins目录 /root/.jenkins\n \n\n\n> 复制文件夹内的jenkins.war到tomcat目录下的webapps目录下 并启动tomcat (./startup.sh)\n\n> 进入: cd ~/.jenkins/ \n\n 目录介绍:\n jobs: 项目配置目录(新建的job,即一个个项目 的配置存放在这里)\n logs: 日志目录\n workspaces: 默认的工作空间(每个项目会存放在这里)\n users: 创建的用户目录\n secrets: 初始的设置(包括初始化的密码, 在创建了用户以后会自动删除里面的初始化密码文件)\n\n\n> tomcat启动以后 访问网页 http://127.0.0.1/jenkins 提示输入初始化密码, 密码在secrets目录下的initialAdminPassword文件内\n> 进入后先创建一个用户(以后就用这个用户登录), 然后提示下载插件(可以下载推荐的也可以自定义)\n\n> ##按照提示新建一个项目 如图:这里测试从码云获取项目,so选择第一个, 设置完后 保存\n\n(如果是maven项目 需要下载个maven插件-> Maven Integration plugin 在设置里下载 安装重启 下面介绍)\n\n{% qnimg jenkins.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n 填写内容:\n 项目名称: 最好和真正的项目名相同\n 描述: 随意\n \n 源码管理: 选择 git (当遇到 401, 没权限, 读取远程key失败请看下面错误解决)\n 构建触发器: 设置什么时候触发构建功能(可以用脚本,或者有人提交代码到码云时候构建,或者定时构建都可以)\n \n (jenkins有各种提示, 都介绍的很详细了) \n \n \n{% qnimg jenkins_a.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n{% qnimg jenkins_b.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n*如果项目是私有的项目(如码云中的), 则需要设置用户名密码--在 Credentials 中配置 Add 一个*\n{% qnimg jenkins_c.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n{% qnimg jenkins_maven.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n> 点击左侧 立即构建\n> 到主面板查看项目构建情况: 红色代表失败, 蓝色表示成功\n\n*可以再面板的 系统管理/系统设置 中修改设置*\n\n\n 系统设置:\n 1.点击高级设置: 可以修改 工作空间根目录,构建记录根目录\n 2.可以修改时间格式\n 3.可以修改ssh端口,管理员邮件地址,Jenkins URL等等\n Global Tool Configuration:\n 1.设置maven路径\n 2.设置jdk路径\n 3.设置git文件路径\n \n\n> ##对于maven项目 配置如图一,可自行更改\n{% qnimg jenkins_maven2.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n下图是点击构建以后打印的日志信息(正在下载依赖) (but有个问题 下载的jar包并没有按照我maven中配置的仓库地址,而是下载到了.jenkins目录下)\n{% qnimg jenkins_maven.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n\n> ##发布war到tomcat: 由于这里编译就和自己电脑打包项目一样,会把war包放在target目录下, 我们需要一个插件把其发送到tomcat的webapps中\n\n> (下载插件 Deploy to container Plugin)\n\n>安装插件以后进入项目的配置界面 设置\"构建后操作\",里面就会多一个 \"Deploy war/ear to a container\" 选项\n\n\n 配置:\n WAR/EAR files: target/*.war (war包的相对路径,相对工作空间)\n Context path: MeetCode (发布到tomcat的路径,这里我用项目名,到时候访问就用http://127.0.0.1:80/MeetCode)\n \n Containers: 配置一个tomcat容器(我用的tomcat8,但是我的版本没有 只能用7x)\n Manager user name: (tomcat用户名: 进入tomcat/conf/tomcat-users.xml设置)\n Manager password: 密码\n Tomcat URL: http://127.0.0.1:80 (设置远程tomcat访问路径)\n \n \n\n>设置完毕 保存 构建 可看日志观察构建过程 (进入tomcat/webapps下 发现war包正在传入), success之后访问网页可用\n\n> ## 其实也可以不发送war包, 可以在Post Steps中配置maven运行命令*\n\n 要求: 1. 在pom文件中加入tomcat插件\n 2. 如下图配置,则构建最后会直接通过maven运行启动项目\n \n 其他问题: 运行其实依然处于正在构建状态(可以自己查看项目构建状态),\n 所以需要在项目配置中勾选\"丢弃旧的构建\",保持构建的最大个数填写\"1\",(也可以设置构建最大时间);\n\n{% qnimg blog_jenkins.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n\n> ##设置邮件消息\n 1.进入 系统管理 / 系统设置 / 邮件通知\n \n 设置选项: (这里用qq邮箱测试)\n SMTP服务器: smtp.qq.com\n 用户默认邮件后缀: @qq.com\n 使用SMTP认证: true\n \t 用户名: (作为发件箱的qq邮箱,可以带或不带后缀)\n \t 密码: (一般是邮箱密码,qq邮箱特殊,需要进入qq邮箱/设置/账户设置, 发送短信生成一个smtp密码)\n \t 使用SSL协议: false\n \t SMTP端口: 587\n \t Reply-To Address: 我没写\n \t 字符集: UTF-8\n \t \n \t 测试: 写另一个邮箱地址进行测试\n 注意: 如果失败, 提示com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be sam\n 需要设置\t系统管理员邮件地址, 在本页面上面,必须与发件箱一致\n \n---\n\n## 错误解决:\n> 问题: 设置git地址时候 遇到401错误 或者 fatal: Could not read from remote repository\n\n> 原因: 本地git没有配置SSH公钥 \n\n* 这个配置在github中在右上角 Settings/SSH keys and GPG keys 中\n* 在码云中在右上角 修改资料/SSH公钥 中(二者命令不同)\n\n\n\n github生成公钥 命令:\n git config --global user.name \"XXXXX\"\n git config --global user.email \"XXXXXXX@gmail.com\"\n 1.查看是否已经有了ssh密钥:cd ~/.ssh\n 如果没有密钥则不会有此文件夹,有则备份删除\n 2.生存密钥:\n ssh-keygen -t rsa -C \"XXXXXXX@gmail.com\"\n 按3个回车,密码为空。\n 此时生成两个文件:id_rsa和id_rsa.pub 公钥在id_rsa.pub中,查看并复制粘贴到上面github的配公钥的地方,添加一个公钥\n 并且在本地也添加公钥\n ssh-add id_rsa.pub\n \n 码云生成公钥 命令: 见 http://git.mydoc.io/?t=154712\n \n","slug":"20170504_jenkins","published":1,"date":"2020-01-15T05:50:39.608Z","updated":"2018-01-13T02:29:22.157Z","layout":"post","photos":[],"link":"","_id":"ckm3inuut000i24ujj8x5ren5","content":"

###1.下载jenkins 官网下载地址,自己选取对应系统 https://jenkins.io/download/

\n
\n

下载rpm或者直接下载war包 https://pkg.jenkins.io/redhat/jenkins-2.9-1.1.noarch.rpm

\n
\n
系统环境要求: jdk, tomcat, git, maven (没有就安装先)\n
\n

rpm -ivh jenkins-2.9-1.1.noarch.rpm 我的安装到 /usr/lib/jenkins目录

\n
\n
Jenkins主目录是用户目录下的.jenkins目录  /root/.jenkins\n
\n
\n

复制文件夹内的jenkins.war到tomcat目录下的webapps目录下 并启动tomcat (./startup.sh)

\n
\n
\n

进入: cd ~/.jenkins/

\n
\n
目录介绍:\n    jobs: 项目配置目录(新建的job,即一个个项目 的配置存放在这里)\n    logs: 日志目录\n    workspaces: 默认的工作空间(每个项目会存放在这里)\n    users: 创建的用户目录\n    secrets: 初始的设置(包括初始化的密码, 在创建了用户以后会自动删除里面的初始化密码文件)\n
\n

tomcat启动以后 访问网页 http://127.0.0.1/jenkins 提示输入初始化密码, 密码在secrets目录下的initialAdminPassword文件内
进入后先创建一个用户(以后就用这个用户登录), 然后提示下载插件(可以下载推荐的也可以自定义)

\n
\n
\n

##按照提示新建一个项目 如图:这里测试从码云获取项目,so选择第一个, 设置完后 保存

\n
\n

(如果是maven项目 需要下载个maven插件-> Maven Integration plugin 在设置里下载 安装重启 下面介绍)

\n\"图片说明\"\n
填写内容:\n    项目名称: 最好和真正的项目名相同\n    描述: 随意\n\n    源码管理: 选择 git (当遇到 401, 没权限, 读取远程key失败请看下面错误解决)\n    构建触发器: 设置什么时候触发构建功能(可以用脚本,或者有人提交代码到码云时候构建,或者定时构建都可以)\n\n    (jenkins有各种提示, 都介绍的很详细了)   \n
\"图片说明\"\n\"图片说明\"\n

如果项目是私有的项目(如码云中的), 则需要设置用户名密码–在 Credentials 中配置 Add 一个
\"图片说明\"

\n\"图片说明\"\n
\n

点击左侧 立即构建
到主面板查看项目构建情况: 红色代表失败, 蓝色表示成功

\n
\n

可以再面板的 系统管理/系统设置 中修改设置

\n
系统设置:\n    1.点击高级设置: 可以修改 工作空间根目录,构建记录根目录\n    2.可以修改时间格式\n    3.可以修改ssh端口,管理员邮件地址,Jenkins URL等等\nGlobal Tool Configuration:\n    1.设置maven路径\n    2.设置jdk路径\n    3.设置git文件路径\n
\n

##对于maven项目 配置如图一,可自行更改
\"图片说明\"

\n
\n

下图是点击构建以后打印的日志信息(正在下载依赖) (but有个问题 下载的jar包并没有按照我maven中配置的仓库地址,而是下载到了.jenkins目录下)
\"图片说明\"

\n
\n

##发布war到tomcat: 由于这里编译就和自己电脑打包项目一样,会把war包放在target目录下, 我们需要一个插件把其发送到tomcat的webapps中

\n
\n
\n

(下载插件 Deploy to container Plugin)

\n
\n
\n

安装插件以后进入项目的配置界面 设置”构建后操作”,里面就会多一个 “Deploy war/ear to a container” 选项

\n
\n
配置:\n    WAR/EAR files: target/*.war  (war包的相对路径,相对工作空间)\n    Context path: MeetCode (发布到tomcat的路径,这里我用项目名,到时候访问就用http://127.0.0.1:80/MeetCode)\n\n    Containers: 配置一个tomcat容器(我用的tomcat8,但是我的版本没有 只能用7x)\n            Manager user name: (tomcat用户名: 进入tomcat/conf/tomcat-users.xml设置)\n            Manager password: 密码\n            Tomcat URL: http://127.0.0.1:80 (设置远程tomcat访问路径)\n
\n

设置完毕 保存 构建 可看日志观察构建过程 (进入tomcat/webapps下 发现war包正在传入), success之后访问网页可用

\n
\n
\n

其实也可以不发送war包, 可以在Post Steps中配置maven运行命令*

\n
要求: 1. 在pom文件中加入tomcat插件\n      2. 如下图配置,则构建最后会直接通过maven运行启动项目\n\n其他问题: 运行其实依然处于正在构建状态(可以自己查看项目构建状态),\n        所以需要在项目配置中勾选"丢弃旧的构建",保持构建的最大个数填写"1",(也可以设置构建最大时间);\n
\"图片说明\"\n
\n

##设置邮件消息
1.进入 系统管理 / 系统设置 / 邮件通知

\n
\n
设置选项: (这里用qq邮箱测试)\n    SMTP服务器: smtp.qq.com\n    用户默认邮件后缀: @qq.com\n    使用SMTP认证: true\n     用户名: (作为发件箱的qq邮箱,可以带或不带后缀)\n     密码: (一般是邮箱密码,qq邮箱特殊,需要进入qq邮箱/设置/账户设置, 发送短信生成一个smtp密码)\n     使用SSL协议: false\n     SMTP端口: 587\n     Reply-To Address: 我没写\n     字符集: UTF-8\n\n     测试: 写另一个邮箱地址进行测试\n    注意: 如果失败, 提示com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be sam\n    需要设置    系统管理员邮件地址, 在本页面上面,必须与发件箱一致\n

\n

错误解决:

\n

问题: 设置git地址时候 遇到401错误 或者 fatal: Could not read from remote repository

\n
\n
\n

原因: 本地git没有配置SSH公钥

\n
\n\n
github生成公钥 命令:\n         git config --global user.name "XXXXX"\n         git config --global user.email "XXXXXXX@gmail.com"\n     1.查看是否已经有了ssh密钥:cd ~/.ssh\n     如果没有密钥则不会有此文件夹,有则备份删除\n     2.生存密钥:\n         ssh-keygen -t rsa -C "XXXXXXX@gmail.com"\n     按3个回车,密码为空。\n     此时生成两个文件:id_rsa和id_rsa.pub 公钥在id_rsa.pub中,查看并复制粘贴到上面github的配公钥的地方,添加一个公钥\n     并且在本地也添加公钥\n         ssh-add id_rsa.pub\n\n码云生成公钥 命令: 见 http://git.mydoc.io/?t=154712\n
","site":{"data":{}},"excerpt":"

###1.下载jenkins 官网下载地址,自己选取对应系统 https://jenkins.io/download/

\n
\n

下载rpm或者直接下载war包 https://pkg.jenkins.io/redhat/jenkins-2.9-1.1.noarch.rpm

\n
\n
系统环境要求: jdk, tomcat, git, maven (没有就安装先)\n
\n

rpm -ivh jenkins-2.9-1.1.noarch.rpm 我的安装到 /usr/lib/jenkins目录

\n
\n
Jenkins主目录是用户目录下的.jenkins目录  /root/.jenkins\n
","more":"
\n

复制文件夹内的jenkins.war到tomcat目录下的webapps目录下 并启动tomcat (./startup.sh)

\n
\n
\n

进入: cd ~/.jenkins/

\n
\n
目录介绍:\n    jobs: 项目配置目录(新建的job,即一个个项目 的配置存放在这里)\n    logs: 日志目录\n    workspaces: 默认的工作空间(每个项目会存放在这里)\n    users: 创建的用户目录\n    secrets: 初始的设置(包括初始化的密码, 在创建了用户以后会自动删除里面的初始化密码文件)\n
\n

tomcat启动以后 访问网页 http://127.0.0.1/jenkins 提示输入初始化密码, 密码在secrets目录下的initialAdminPassword文件内
进入后先创建一个用户(以后就用这个用户登录), 然后提示下载插件(可以下载推荐的也可以自定义)

\n
\n
\n

##按照提示新建一个项目 如图:这里测试从码云获取项目,so选择第一个, 设置完后 保存

\n
\n

(如果是maven项目 需要下载个maven插件-> Maven Integration plugin 在设置里下载 安装重启 下面介绍)

\n\"图片说明\"\n
填写内容:\n    项目名称: 最好和真正的项目名相同\n    描述: 随意\n\n    源码管理: 选择 git (当遇到 401, 没权限, 读取远程key失败请看下面错误解决)\n    构建触发器: 设置什么时候触发构建功能(可以用脚本,或者有人提交代码到码云时候构建,或者定时构建都可以)\n\n    (jenkins有各种提示, 都介绍的很详细了)   \n
\"图片说明\"\n\"图片说明\"\n

如果项目是私有的项目(如码云中的), 则需要设置用户名密码–在 Credentials 中配置 Add 一个
\"图片说明\"

\n\"图片说明\"\n
\n

点击左侧 立即构建
到主面板查看项目构建情况: 红色代表失败, 蓝色表示成功

\n
\n

可以再面板的 系统管理/系统设置 中修改设置

\n
系统设置:\n    1.点击高级设置: 可以修改 工作空间根目录,构建记录根目录\n    2.可以修改时间格式\n    3.可以修改ssh端口,管理员邮件地址,Jenkins URL等等\nGlobal Tool Configuration:\n    1.设置maven路径\n    2.设置jdk路径\n    3.设置git文件路径\n
\n

##对于maven项目 配置如图一,可自行更改
\"图片说明\"

\n
\n

下图是点击构建以后打印的日志信息(正在下载依赖) (but有个问题 下载的jar包并没有按照我maven中配置的仓库地址,而是下载到了.jenkins目录下)
\"图片说明\"

\n
\n

##发布war到tomcat: 由于这里编译就和自己电脑打包项目一样,会把war包放在target目录下, 我们需要一个插件把其发送到tomcat的webapps中

\n
\n
\n

(下载插件 Deploy to container Plugin)

\n
\n
\n

安装插件以后进入项目的配置界面 设置”构建后操作”,里面就会多一个 “Deploy war/ear to a container” 选项

\n
\n
配置:\n    WAR/EAR files: target/*.war  (war包的相对路径,相对工作空间)\n    Context path: MeetCode (发布到tomcat的路径,这里我用项目名,到时候访问就用http://127.0.0.1:80/MeetCode)\n\n    Containers: 配置一个tomcat容器(我用的tomcat8,但是我的版本没有 只能用7x)\n            Manager user name: (tomcat用户名: 进入tomcat/conf/tomcat-users.xml设置)\n            Manager password: 密码\n            Tomcat URL: http://127.0.0.1:80 (设置远程tomcat访问路径)\n
\n

设置完毕 保存 构建 可看日志观察构建过程 (进入tomcat/webapps下 发现war包正在传入), success之后访问网页可用

\n
\n
\n

其实也可以不发送war包, 可以在Post Steps中配置maven运行命令*

\n
要求: 1. 在pom文件中加入tomcat插件\n      2. 如下图配置,则构建最后会直接通过maven运行启动项目\n\n其他问题: 运行其实依然处于正在构建状态(可以自己查看项目构建状态),\n        所以需要在项目配置中勾选"丢弃旧的构建",保持构建的最大个数填写"1",(也可以设置构建最大时间);\n
\"图片说明\"\n
\n

##设置邮件消息
1.进入 系统管理 / 系统设置 / 邮件通知

\n
\n
设置选项: (这里用qq邮箱测试)\n    SMTP服务器: smtp.qq.com\n    用户默认邮件后缀: @qq.com\n    使用SMTP认证: true\n     用户名: (作为发件箱的qq邮箱,可以带或不带后缀)\n     密码: (一般是邮箱密码,qq邮箱特殊,需要进入qq邮箱/设置/账户设置, 发送短信生成一个smtp密码)\n     使用SSL协议: false\n     SMTP端口: 587\n     Reply-To Address: 我没写\n     字符集: UTF-8\n\n     测试: 写另一个邮箱地址进行测试\n    注意: 如果失败, 提示com.sun.mail.smtp.SMTPSendFailedException: 501 mail from address must be sam\n    需要设置    系统管理员邮件地址, 在本页面上面,必须与发件箱一致\n

\n

错误解决:

\n

问题: 设置git地址时候 遇到401错误 或者 fatal: Could not read from remote repository

\n
\n
\n

原因: 本地git没有配置SSH公钥

\n
\n\n
github生成公钥 命令:\n         git config --global user.name "XXXXX"\n         git config --global user.email "XXXXXXX@gmail.com"\n     1.查看是否已经有了ssh密钥:cd ~/.ssh\n     如果没有密钥则不会有此文件夹,有则备份删除\n     2.生存密钥:\n         ssh-keygen -t rsa -C "XXXXXXX@gmail.com"\n     按3个回车,密码为空。\n     此时生成两个文件:id_rsa和id_rsa.pub 公钥在id_rsa.pub中,查看并复制粘贴到上面github的配公钥的地方,添加一个公钥\n     并且在本地也添加公钥\n         ssh-add id_rsa.pub\n\n码云生成公钥 命令: 见 http://git.mydoc.io/?t=154712\n
"},{"title":"重新学习c语言,零碎的知识点","comments":1,"description":null,"_content":"\n1. 编译过程4步骤:\n\n 1. 前提: 一个helloworld.c文件 执行 gcc helloworld.c命令进行编译;\n 2. 过程: \n .c文件 --> . i文件 :预处理\n \n 预处理之一: (.i文件也是c语法)把头文件展开 \n 预处理之二: 宏替换(#define),即把代码中的宏定义替换为定义的字符串(单纯的字符串替换,不识别类型))\n ps:宏定义内容可以是任意字符串, 只要替换以后符合c语法即可(可以是表达式)\n ps:宏定义#define和typedef不同,typedef(关键字)是为变量类型起别名,而#define可以为任意字符串任意字符串的\"别名\",而且typedef在预处理时候不会被替换\n \n .i文件 --> .s文件 :编译\n .s文件 --> .o文件 :汇编\n .o文件 --> 可执行文件 :链接\n\n\n\n2. 结构体定义:\n\n```c\n struct student {\n char name[20];\n int age;\n int price;\n } stu; //这里是定义一个结构体 名为stu 类型为 struct student\n \n struct {\n char name[20];\n int age;\n int price;\n } stuO; //这里是定义一个结构体 名为stu 并且不能再定义其他的相同的结构体变量\n \n typedef struct student2 {\n char name[20];\n int age;\n int price;\n } stu2; //这里是定义一个结构体的别名 名为stu2\n \n int main() {\n struct student2 ss = {\"aaaaaa\", 20, 31}; //定义一个结构体 ss是结构体的引用\n struct student2 ss2[2] = {{\"aaaaaa\", 20, 31}, {\"bbbb\", 21, 32}}; //定义一个结构体数组\n printf(\"______________ %s\", ss.name);\n printf(\"______________ %d\", ss.age);\n }\n```\n\n3. 结构体指针:\n\n```c\n struct student2 *sNode;\n struct student2 *sNode2;\n sNode = &ss;\n sNode2 = &ss2;//指向结构体数组的第一个元素的地址 可以进行 sNode2++ 就指向第二个元素\n \n printf(\"______________ %d\", (*sNode).age);\n printf(\"______________ %d\", sNode->age); // ->是 指向运算符\n printf(\"______________ %d\", sNode2->age); \n \n```\n\n4. 共用体:\n\n```c \n union data {\n int a;\n char b;\n char c[5]; //此处共用体占用的空间大小是8 (结构体对象大小=最后一个成员的偏移量+最后一个成员的大小+末尾填充字节数), 见 内存对齐机制 http://blog.csdn.net/fb2058/article/details/15502071\n int d;\n };\n \n //偏移量: 某个成员的实际地址和结构体首地址之间的距离\n \n int main() {\n union data dd = {10}; //共用体成员共享一块空间(是成员之间 so一个成员被赋值,其他的成员跟着变)\n dd.b = 'a';\n \n printf(\"___SIZE____ %lu\\n\", sizeof(dd));\n printf(\"________ %d\", dd.a);\n printf(\"________ %c\", dd.b);\n printf(\"________ %s\", dd.c);\n printf(\"________ %d\", dd.d);\n }\n\n```","source":"_posts/20170507_c_note.md","raw":"---\ntitle: 重新学习c语言,零碎的知识点 \n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n\n\ndescription: \n\n---\n\n1. 编译过程4步骤:\n\n 1. 前提: 一个helloworld.c文件 执行 gcc helloworld.c命令进行编译;\n 2. 过程: \n .c文件 --> . i文件 :预处理\n \n 预处理之一: (.i文件也是c语法)把头文件展开 \n 预处理之二: 宏替换(#define),即把代码中的宏定义替换为定义的字符串(单纯的字符串替换,不识别类型))\n ps:宏定义内容可以是任意字符串, 只要替换以后符合c语法即可(可以是表达式)\n ps:宏定义#define和typedef不同,typedef(关键字)是为变量类型起别名,而#define可以为任意字符串任意字符串的\"别名\",而且typedef在预处理时候不会被替换\n \n .i文件 --> .s文件 :编译\n .s文件 --> .o文件 :汇编\n .o文件 --> 可执行文件 :链接\n\n\n\n2. 结构体定义:\n\n```c\n struct student {\n char name[20];\n int age;\n int price;\n } stu; //这里是定义一个结构体 名为stu 类型为 struct student\n \n struct {\n char name[20];\n int age;\n int price;\n } stuO; //这里是定义一个结构体 名为stu 并且不能再定义其他的相同的结构体变量\n \n typedef struct student2 {\n char name[20];\n int age;\n int price;\n } stu2; //这里是定义一个结构体的别名 名为stu2\n \n int main() {\n struct student2 ss = {\"aaaaaa\", 20, 31}; //定义一个结构体 ss是结构体的引用\n struct student2 ss2[2] = {{\"aaaaaa\", 20, 31}, {\"bbbb\", 21, 32}}; //定义一个结构体数组\n printf(\"______________ %s\", ss.name);\n printf(\"______________ %d\", ss.age);\n }\n```\n\n3. 结构体指针:\n\n```c\n struct student2 *sNode;\n struct student2 *sNode2;\n sNode = &ss;\n sNode2 = &ss2;//指向结构体数组的第一个元素的地址 可以进行 sNode2++ 就指向第二个元素\n \n printf(\"______________ %d\", (*sNode).age);\n printf(\"______________ %d\", sNode->age); // ->是 指向运算符\n printf(\"______________ %d\", sNode2->age); \n \n```\n\n4. 共用体:\n\n```c \n union data {\n int a;\n char b;\n char c[5]; //此处共用体占用的空间大小是8 (结构体对象大小=最后一个成员的偏移量+最后一个成员的大小+末尾填充字节数), 见 内存对齐机制 http://blog.csdn.net/fb2058/article/details/15502071\n int d;\n };\n \n //偏移量: 某个成员的实际地址和结构体首地址之间的距离\n \n int main() {\n union data dd = {10}; //共用体成员共享一块空间(是成员之间 so一个成员被赋值,其他的成员跟着变)\n dd.b = 'a';\n \n printf(\"___SIZE____ %lu\\n\", sizeof(dd));\n printf(\"________ %d\", dd.a);\n printf(\"________ %c\", dd.b);\n printf(\"________ %s\", dd.c);\n printf(\"________ %d\", dd.d);\n }\n\n```","slug":"20170507_c_note","published":1,"date":"2020-01-15T05:50:39.610Z","updated":"2018-01-13T02:29:22.161Z","layout":"post","photos":[],"link":"","_id":"ckm3inuuu000j24ujiamq9qrx","content":"
    \n
  1. 编译过程4步骤:

    \n
      \n
    1. 前提: 一个helloworld.c文件 执行 gcc helloworld.c命令进行编译;
    2. \n
    3. 过程:
      .c文件 –> . i文件 :预处理

      \n
      预处理之一: (.i文件也是c语法)把头文件展开 \n预处理之二: 宏替换(#define),即把代码中的宏定义替换为定义的字符串(单纯的字符串替换,不识别类型))\n            ps:宏定义内容可以是任意字符串, 只要替换以后符合c语法即可(可以是表达式)\n            ps:宏定义#define和typedef不同,typedef(关键字)是为变量类型起别名,而#define可以为任意字符串任意字符串的"别名",而且typedef在预处理时候不会被替换\n

      .i文件 –> .s文件 :编译
      .s文件 –> .o文件 :汇编
      .o文件 –> 可执行文件 :链接

      \n
    4. \n
    \n
  2. \n
\n\n
    \n
  1. 结构体定义:
  2. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct student {
char name[20];
int age;
int price;
} stu; //这里是定义一个结构体 名为stu 类型为 struct student

struct {
char name[20];
int age;
int price;
} stuO; //这里是定义一个结构体 名为stu 并且不能再定义其他的相同的结构体变量

typedef struct student2 {
char name[20];
int age;
int price;
} stu2; //这里是定义一个结构体的别名 名为stu2

int main() {
struct student2 ss = {\"aaaaaa\", 20, 31}; //定义一个结构体 ss是结构体的引用
struct student2 ss2[2] = {{\"aaaaaa\", 20, 31}, {\"bbbb\", 21, 32}}; //定义一个结构体数组
printf(\"______________ %s\", ss.name);
printf(\"______________ %d\", ss.age);
}
\n
    \n
  1. 结构体指针:
  2. \n
\n
1
2
3
4
5
6
7
8
struct student2 *sNode;
struct student2 *sNode2;
sNode = &ss;
sNode2 = &ss2;//指向结构体数组的第一个元素的地址 可以进行 sNode2++ 就指向第二个元素

printf(\"______________ %d\", (*sNode).age);
printf(\"______________ %d\", sNode->age); // ->是 指向运算符
printf(\"______________ %d\", sNode2->age);
\n
    \n
  1. 共用体:
  2. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
union data {
int a;
char b;
char c[5]; //此处共用体占用的空间大小是8 (结构体对象大小=最后一个成员的偏移量+最后一个成员的大小+末尾填充字节数), 见 内存对齐机制 http://blog.csdn.net/fb2058/article/details/15502071
int d;
};

//偏移量: 某个成员的实际地址和结构体首地址之间的距离

int main() {
union data dd = {10}; //共用体成员共享一块空间(是成员之间 so一个成员被赋值,其他的成员跟着变)
dd.b = 'a';

printf(\"___SIZE____ %lu\\n\", sizeof(dd));
printf(\"________ %d\", dd.a);
printf(\"________ %c\", dd.b);
printf(\"________ %s\", dd.c);
printf(\"________ %d\", dd.d);
}
","site":{"data":{}},"excerpt":"
    \n
  1. 编译过程4步骤:

    \n
      \n
    1. 前提: 一个helloworld.c文件 执行 gcc helloworld.c命令进行编译;
    2. \n
    3. 过程:
      .c文件 –> . i文件 :预处理

      \n
      预处理之一: (.i文件也是c语法)把头文件展开 \n预处理之二: 宏替换(#define),即把代码中的宏定义替换为定义的字符串(单纯的字符串替换,不识别类型))\n            ps:宏定义内容可以是任意字符串, 只要替换以后符合c语法即可(可以是表达式)\n            ps:宏定义#define和typedef不同,typedef(关键字)是为变量类型起别名,而#define可以为任意字符串任意字符串的"别名",而且typedef在预处理时候不会被替换\n

      .i文件 –> .s文件 :编译
      .s文件 –> .o文件 :汇编
      .o文件 –> 可执行文件 :链接

      \n
    4. \n
    \n
  2. \n
","more":"
    \n
  1. 结构体定义:
  2. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct student {
char name[20];
int age;
int price;
} stu; //这里是定义一个结构体 名为stu 类型为 struct student

struct {
char name[20];
int age;
int price;
} stuO; //这里是定义一个结构体 名为stu 并且不能再定义其他的相同的结构体变量

typedef struct student2 {
char name[20];
int age;
int price;
} stu2; //这里是定义一个结构体的别名 名为stu2

int main() {
struct student2 ss = {\"aaaaaa\", 20, 31}; //定义一个结构体 ss是结构体的引用
struct student2 ss2[2] = {{\"aaaaaa\", 20, 31}, {\"bbbb\", 21, 32}}; //定义一个结构体数组
printf(\"______________ %s\", ss.name);
printf(\"______________ %d\", ss.age);
}
\n
    \n
  1. 结构体指针:
  2. \n
\n
1
2
3
4
5
6
7
8
struct student2 *sNode;
struct student2 *sNode2;
sNode = &ss;
sNode2 = &ss2;//指向结构体数组的第一个元素的地址 可以进行 sNode2++ 就指向第二个元素

printf(\"______________ %d\", (*sNode).age);
printf(\"______________ %d\", sNode->age); // ->是 指向运算符
printf(\"______________ %d\", sNode2->age);
\n
    \n
  1. 共用体:
  2. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
union data {
int a;
char b;
char c[5]; //此处共用体占用的空间大小是8 (结构体对象大小=最后一个成员的偏移量+最后一个成员的大小+末尾填充字节数), 见 内存对齐机制 http://blog.csdn.net/fb2058/article/details/15502071
int d;
};

//偏移量: 某个成员的实际地址和结构体首地址之间的距离

int main() {
union data dd = {10}; //共用体成员共享一块空间(是成员之间 so一个成员被赋值,其他的成员跟着变)
dd.b = 'a';

printf(\"___SIZE____ %lu\\n\", sizeof(dd));
printf(\"________ %d\", dd.a);
printf(\"________ %c\", dd.b);
printf(\"________ %s\", dd.c);
printf(\"________ %d\", dd.d);
}
"},{"title":"枚举法 (实例)","comments":1,"description":null,"_content":"\n\n## 使用枚举法(穷举法)实现填运算符游戏\n\n> 要求: 输入5个数字和1个结果 其中有4个位置选择4种运算符, 实现5个数字通过使用运算符计算,得到填入的结果;\n\n> 例子: 填入 5 5 5 5 5 = 5 得到 5 + 5 - 5 * 5 / 5 = 5 (实际情况中可能有多种解)\n \n\n\n \n \n #include \n #include \n \n int main()\n {\n //下面的所有的数组中第一个位置都不使用(下标为0)\n int j, i[5];//这里i的4个属性表示4个位置的运算符(什么运算符并不知)\n int sign;//累加运算事时的符号(需要把所有的加减法转化为乘除已杜绝运算符优先级的问题)\n int result;//保存运算式的结果值\n int count=0;//计数器\n int num[6];//操作数, 存放5个要进行计算的数字\n float left, right; // 要把多个数的运算转换为多次的二元运算 left每次存放上次两个数的计算结果\n char oper[5] = {' ', '+', '-', '*', '/'}; //下标为1 2 3 4位置分别表示 + - * / (由oper数组中属性位置而定, 例如 )\n \n printf(\"请输入5个数\\n\");\n for(j=1; j<=5; j++){\n scanf(\"%d\", &num[j]);\n }\n printf(\"请输入结果\\n\");\n scanf(\"%d\", &result);\n \n for(i[1] = 1;i[1] <= 4;i[1]++){//循环第一个位置的运算符\n // 只有两种情况可以进行下一步: 1,不是i[n]<4(即不是除法) 2,是i[n]==4而且下一个数字不是0(因为除数不能是0)\n if((i[1] < 4) || (num[2] != 0)){\n for(i[2] = 1;i[2] < 4;i[2]++){//循环第二个位置的运算符\n if((i[2] < 4) || (num[3] !=0)){\n for(i[3] = 1; i[3] <= 4; i[3]++){//循环第三个位置的运算符\n if((i[3] < 4) || num[4] != 0){\n for(i[4] = 0; i[4] <= 4; i[4]++){//循环第四个位置的运算符\n if((i[4] < 4) || (num[5] != 0)){\n \n //四重循环以后 就得到所有的4个位置的运算符的所有的组合情况\n \n left = 0;//设置初始\n right = num[1];//设置初始\n sign = 1; // 1正数 -1负数\n \n //printf(\"[%d %d %d %d]\\t\", i[1], i[2], i[3], i[4]);\n \n for(j=1;j<=4;j++){//开始组合4个运算符并完成计算\n //下次进入循环的时候才计算这次的结果, 所以需要在循环外面加上最后一次的right\n //如果是+-,会把以前的结果(left和right)存放到left中,把要+-的对象存放到right,等待以后循环时候执行+-操作\n //如果是*/,会把本次的*/的双方直接进行操作, 把结果存放到right中,等待以后循环的时候把right在加入到left中\n //通过以上两种处理结果, 在最终循环外部把left和right相加(或相减,取决于sign)\n switch(oper[i[j]])\n {\n case '+':\n left = left + sign * right;\n sign = 1;\n right = num[j+1];\n break;\n case '-':\n left = left + sign * right;\n sign = -1;\n right = num[j+1];\n break;\n case '*':\n right = right * num[j+1];\n break;\n case '/':\n right = right / num[j+1];\n break;\n }\n }\n \n if(left + sign * right == result){//对比得到结果和设置的结果\n count++;\n printf(\"%3d\\t\", count);\n for(j = 1; j <= 4; j++){\n printf(\"%d%c\", num[j], oper[i[j]]);\n }\n printf(\"%d=%d\\n\", num[5], result);\n }\n }\n }\n }\n }\n }\n }\n }\n }\n \n if(count == 0){\n printf(\"没有符合要求的方法!\");\n }\n \n getch();\n return 0;\n }\n","source":"_posts/20170713_qjf.md","raw":"---\ntitle: 枚举法 (实例)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n\n## 使用枚举法(穷举法)实现填运算符游戏\n\n> 要求: 输入5个数字和1个结果 其中有4个位置选择4种运算符, 实现5个数字通过使用运算符计算,得到填入的结果;\n\n> 例子: 填入 5 5 5 5 5 = 5 得到 5 + 5 - 5 * 5 / 5 = 5 (实际情况中可能有多种解)\n \n\n\n \n \n #include \n #include \n \n int main()\n {\n //下面的所有的数组中第一个位置都不使用(下标为0)\n int j, i[5];//这里i的4个属性表示4个位置的运算符(什么运算符并不知)\n int sign;//累加运算事时的符号(需要把所有的加减法转化为乘除已杜绝运算符优先级的问题)\n int result;//保存运算式的结果值\n int count=0;//计数器\n int num[6];//操作数, 存放5个要进行计算的数字\n float left, right; // 要把多个数的运算转换为多次的二元运算 left每次存放上次两个数的计算结果\n char oper[5] = {' ', '+', '-', '*', '/'}; //下标为1 2 3 4位置分别表示 + - * / (由oper数组中属性位置而定, 例如 )\n \n printf(\"请输入5个数\\n\");\n for(j=1; j<=5; j++){\n scanf(\"%d\", &num[j]);\n }\n printf(\"请输入结果\\n\");\n scanf(\"%d\", &result);\n \n for(i[1] = 1;i[1] <= 4;i[1]++){//循环第一个位置的运算符\n // 只有两种情况可以进行下一步: 1,不是i[n]<4(即不是除法) 2,是i[n]==4而且下一个数字不是0(因为除数不能是0)\n if((i[1] < 4) || (num[2] != 0)){\n for(i[2] = 1;i[2] < 4;i[2]++){//循环第二个位置的运算符\n if((i[2] < 4) || (num[3] !=0)){\n for(i[3] = 1; i[3] <= 4; i[3]++){//循环第三个位置的运算符\n if((i[3] < 4) || num[4] != 0){\n for(i[4] = 0; i[4] <= 4; i[4]++){//循环第四个位置的运算符\n if((i[4] < 4) || (num[5] != 0)){\n \n //四重循环以后 就得到所有的4个位置的运算符的所有的组合情况\n \n left = 0;//设置初始\n right = num[1];//设置初始\n sign = 1; // 1正数 -1负数\n \n //printf(\"[%d %d %d %d]\\t\", i[1], i[2], i[3], i[4]);\n \n for(j=1;j<=4;j++){//开始组合4个运算符并完成计算\n //下次进入循环的时候才计算这次的结果, 所以需要在循环外面加上最后一次的right\n //如果是+-,会把以前的结果(left和right)存放到left中,把要+-的对象存放到right,等待以后循环时候执行+-操作\n //如果是*/,会把本次的*/的双方直接进行操作, 把结果存放到right中,等待以后循环的时候把right在加入到left中\n //通过以上两种处理结果, 在最终循环外部把left和right相加(或相减,取决于sign)\n switch(oper[i[j]])\n {\n case '+':\n left = left + sign * right;\n sign = 1;\n right = num[j+1];\n break;\n case '-':\n left = left + sign * right;\n sign = -1;\n right = num[j+1];\n break;\n case '*':\n right = right * num[j+1];\n break;\n case '/':\n right = right / num[j+1];\n break;\n }\n }\n \n if(left + sign * right == result){//对比得到结果和设置的结果\n count++;\n printf(\"%3d\\t\", count);\n for(j = 1; j <= 4; j++){\n printf(\"%d%c\", num[j], oper[i[j]]);\n }\n printf(\"%d=%d\\n\", num[5], result);\n }\n }\n }\n }\n }\n }\n }\n }\n }\n \n if(count == 0){\n printf(\"没有符合要求的方法!\");\n }\n \n getch();\n return 0;\n }\n","slug":"20170713_qjf","published":1,"date":"2020-01-15T05:50:39.616Z","updated":"2018-01-13T02:29:22.168Z","layout":"post","photos":[],"link":"","_id":"ckm3inuuw000m24ujzbhd4zlg","content":"

使用枚举法(穷举法)实现填运算符游戏

\n

要求: 输入5个数字和1个结果 其中有4个位置选择4种运算符, 实现5个数字通过使用运算符计算,得到填入的结果;

\n
\n
\n

例子: 填入 5 5 5 5 5 = 5 得到 5 + 5 - 5 * 5 / 5 = 5 (实际情况中可能有多种解)

\n
\n\n
#include <stdio.h>\n#include <stdlib.h>\n\nint main()\n{\n    //下面的所有的数组中第一个位置都不使用(下标为0)\n    int j, i[5];//这里i的4个属性表示4个位置的运算符(什么运算符并不知)\n    int sign;//累加运算事时的符号(需要把所有的加减法转化为乘除已杜绝运算符优先级的问题)\n    int result;//保存运算式的结果值\n    int count=0;//计数器\n    int num[6];//操作数, 存放5个要进行计算的数字\n    float left, right; // 要把多个数的运算转换为多次的二元运算 left每次存放上次两个数的计算结果\n    char oper[5] = {' ', '+', '-', '*', '/'}; //下标为1 2 3 4位置分别表示 + - * / (由oper数组中属性位置而定, 例如 )\n\n    printf("请输入5个数\\n");\n    for(j=1; j<=5; j++){\n        scanf("%d", &num[j]);\n    }\n    printf("请输入结果\\n");\n    scanf("%d", &result);\n\n    for(i[1] = 1;i[1] <= 4;i[1]++){//循环第一个位置的运算符\n        // 只有两种情况可以进行下一步: 1,不是i[n]<4(即不是除法) 2,是i[n]==4而且下一个数字不是0(因为除数不能是0)\n        if((i[1] < 4) || (num[2] != 0)){\n            for(i[2] = 1;i[2] < 4;i[2]++){//循环第二个位置的运算符\n                if((i[2] < 4) || (num[3] !=0)){\n                    for(i[3] = 1; i[3] <= 4; i[3]++){//循环第三个位置的运算符\n                        if((i[3] < 4) || num[4] != 0){\n                            for(i[4] = 0; i[4] <= 4; i[4]++){//循环第四个位置的运算符\n                                if((i[4] < 4) || (num[5] != 0)){\n\n                                    //四重循环以后 就得到所有的4个位置的运算符的所有的组合情况\n\n                                    left = 0;//设置初始\n                                    right = num[1];//设置初始\n                                    sign = 1; // 1正数 -1负数\n\n                                    //printf("[%d %d %d %d]\\t", i[1], i[2], i[3], i[4]);\n\n                                    for(j=1;j<=4;j++){//开始组合4个运算符并完成计算\n                                        //下次进入循环的时候才计算这次的结果, 所以需要在循环外面加上最后一次的right\n                                        //如果是+-,会把以前的结果(left和right)存放到left中,把要+-的对象存放到right,等待以后循环时候执行+-操作\n                                        //如果是*/,会把本次的*/的双方直接进行操作, 把结果存放到right中,等待以后循环的时候把right在加入到left中\n                                        //通过以上两种处理结果, 在最终循环外部把left和right相加(或相减,取决于sign)\n                                        switch(oper[i[j]])\n                                        {\n                                            case '+':\n                                                left = left + sign * right;\n                                                sign = 1;\n                                                right = num[j+1];\n                                                break;\n                                            case '-':\n                                                left = left + sign * right;\n                                                sign = -1;\n                                                right = num[j+1];\n                                                break;\n                                            case '*':\n                                                right = right * num[j+1];\n                                                break;\n                                            case '/':\n                                                right = right / num[j+1];\n                                                break;\n                                        }\n                                    }\n\n                                    if(left + sign * right == result){//对比得到结果和设置的结果\n                                        count++;\n                                        printf("%3d\\t", count);\n                                        for(j = 1; j <= 4; j++){\n                                            printf("%d%c", num[j], oper[i[j]]);\n                                        }\n                                        printf("%d=%d\\n", num[5], result);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if(count == 0){\n        printf("没有符合要求的方法!");\n    }\n\n    getch();\n    return 0;\n}\n
","site":{"data":{}},"excerpt":"

使用枚举法(穷举法)实现填运算符游戏

\n

要求: 输入5个数字和1个结果 其中有4个位置选择4种运算符, 实现5个数字通过使用运算符计算,得到填入的结果;

\n
\n
\n

例子: 填入 5 5 5 5 5 = 5 得到 5 + 5 - 5 * 5 / 5 = 5 (实际情况中可能有多种解)

\n
","more":"
#include <stdio.h>\n#include <stdlib.h>\n\nint main()\n{\n    //下面的所有的数组中第一个位置都不使用(下标为0)\n    int j, i[5];//这里i的4个属性表示4个位置的运算符(什么运算符并不知)\n    int sign;//累加运算事时的符号(需要把所有的加减法转化为乘除已杜绝运算符优先级的问题)\n    int result;//保存运算式的结果值\n    int count=0;//计数器\n    int num[6];//操作数, 存放5个要进行计算的数字\n    float left, right; // 要把多个数的运算转换为多次的二元运算 left每次存放上次两个数的计算结果\n    char oper[5] = {' ', '+', '-', '*', '/'}; //下标为1 2 3 4位置分别表示 + - * / (由oper数组中属性位置而定, 例如 )\n\n    printf("请输入5个数\\n");\n    for(j=1; j<=5; j++){\n        scanf("%d", &num[j]);\n    }\n    printf("请输入结果\\n");\n    scanf("%d", &result);\n\n    for(i[1] = 1;i[1] <= 4;i[1]++){//循环第一个位置的运算符\n        // 只有两种情况可以进行下一步: 1,不是i[n]<4(即不是除法) 2,是i[n]==4而且下一个数字不是0(因为除数不能是0)\n        if((i[1] < 4) || (num[2] != 0)){\n            for(i[2] = 1;i[2] < 4;i[2]++){//循环第二个位置的运算符\n                if((i[2] < 4) || (num[3] !=0)){\n                    for(i[3] = 1; i[3] <= 4; i[3]++){//循环第三个位置的运算符\n                        if((i[3] < 4) || num[4] != 0){\n                            for(i[4] = 0; i[4] <= 4; i[4]++){//循环第四个位置的运算符\n                                if((i[4] < 4) || (num[5] != 0)){\n\n                                    //四重循环以后 就得到所有的4个位置的运算符的所有的组合情况\n\n                                    left = 0;//设置初始\n                                    right = num[1];//设置初始\n                                    sign = 1; // 1正数 -1负数\n\n                                    //printf("[%d %d %d %d]\\t", i[1], i[2], i[3], i[4]);\n\n                                    for(j=1;j<=4;j++){//开始组合4个运算符并完成计算\n                                        //下次进入循环的时候才计算这次的结果, 所以需要在循环外面加上最后一次的right\n                                        //如果是+-,会把以前的结果(left和right)存放到left中,把要+-的对象存放到right,等待以后循环时候执行+-操作\n                                        //如果是*/,会把本次的*/的双方直接进行操作, 把结果存放到right中,等待以后循环的时候把right在加入到left中\n                                        //通过以上两种处理结果, 在最终循环外部把left和right相加(或相减,取决于sign)\n                                        switch(oper[i[j]])\n                                        {\n                                            case '+':\n                                                left = left + sign * right;\n                                                sign = 1;\n                                                right = num[j+1];\n                                                break;\n                                            case '-':\n                                                left = left + sign * right;\n                                                sign = -1;\n                                                right = num[j+1];\n                                                break;\n                                            case '*':\n                                                right = right * num[j+1];\n                                                break;\n                                            case '/':\n                                                right = right / num[j+1];\n                                                break;\n                                        }\n                                    }\n\n                                    if(left + sign * right == result){//对比得到结果和设置的结果\n                                        count++;\n                                        printf("%3d\\t", count);\n                                        for(j = 1; j <= 4; j++){\n                                            printf("%d%c", num[j], oper[i[j]]);\n                                        }\n                                        printf("%d=%d\\n", num[5], result);\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if(count == 0){\n        printf("没有符合要求的方法!");\n    }\n\n    getch();\n    return 0;\n}\n
"},{"title":"Junit运行报错","comments":1,"description":null,"_content":"\nmaven项目运行测试程序报错\n\n{% qnimg blog_junit.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n> Exception in thread \"main\" java.lang.NoSuchMethodError: org.junit.platform.commons.util.AnnotationUt\n\n问题所在: 写测试的注解 @Test 时候 idea提示导包, 导入了两个包, 其中第一个包并不是需要的\n\n{% qnimg blog_junits.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n删除即可","source":"_posts/20170615_junit.md","raw":"---\ntitle: Junit运行报错\n\ncomments: true \n\ntags: \n - junit\n\ncategories: \n - BUG解决\n\ndescription: \n\n---\n\nmaven项目运行测试程序报错\n\n{% qnimg blog_junit.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n> Exception in thread \"main\" java.lang.NoSuchMethodError: org.junit.platform.commons.util.AnnotationUt\n\n问题所在: 写测试的注解 @Test 时候 idea提示导包, 导入了两个包, 其中第一个包并不是需要的\n\n{% qnimg blog_junits.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n删除即可","slug":"20170615_junit","published":1,"date":"2020-01-15T05:50:39.611Z","updated":"2018-01-13T02:29:22.163Z","layout":"post","photos":[],"link":"","_id":"ckm3inuuy000n24uj27fgsmkt","content":"

maven项目运行测试程序报错

\n\"图片说明\"\n
\n

Exception in thread “main” java.lang.NoSuchMethodError: org.junit.platform.commons.util.AnnotationUt

\n
\n

问题所在: 写测试的注解 @Test 时候 idea提示导包, 导入了两个包, 其中第一个包并不是需要的

\n\"图片说明\"\n

删除即可

\n","site":{"data":{}},"excerpt":"","more":"

maven项目运行测试程序报错

\n\"图片说明\"\n
\n

Exception in thread “main” java.lang.NoSuchMethodError: org.junit.platform.commons.util.AnnotationUt

\n
\n

问题所在: 写测试的注解 @Test 时候 idea提示导包, 导入了两个包, 其中第一个包并不是需要的

\n\"图片说明\"\n

删除即可

\n"},{"title":"分治法 (实例)","comments":1,"description":null,"_content":"\n\n## 使用分治法解决循环日程表问题\n\n> 要求: 安排比赛日程 每个选手与其他每个选手进行一对一比赛, 一天只与一个选手比赛,不重复比赛,不漏掉;\n\n\n\n\n\n> 例子: 如下图\n \n{% qnimg fzf1.jpg title:分治法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,\n 这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。\n 即一种分目标完成程序算法,简单问题可用二分法完成。\n\n> 分解复杂问题为简单的组合, 如图\n\n{% qnimg fzf2.jpg title:分治法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n \n #include \n #include\n #define MAXN 64\n int a[MAXN + 1][MAXN + 1] = {0}; //从1开始\n \n // 分治算法\n \n void gamecal(int k, int n) {//k表示开始的选手编号, n表示选手个数\n int i, j;\n if(n == 2){\n a[k][1] = k; // 参赛选手编号为k\n a[k][2] = k + 1; // 对阵选手编号为k+1\n a[k + 1][1] = k + 1; // 参赛选手编号为k\n a[k + 1][2] = k; // 对阵选手编号为k+1\n }else {\n gamecal(k, n/2);//递归完成左上角\n gamecal(k + n/2, n/2);//递归完成左下角\n \n //右上角,右下角 通过观察发现与左下角和左上角完全一样。可以通过循环求得\n for(i = k; i< k + n/2; i++){//右上角\n for(j = n/2 + 1; j <= n; j++){\n a[i][j] = a[i + n/2][j - n/2];\n }\n }\n for(i = k + n/2; i < k + n; i++){//右下角\n for(j = n/2 + 1; j <= n; j++){\n a[i][j] = a[i - n/2][j - n/2];\n }\n }\n \n }\n }\n \n int main(){\n int m,i,j;\n printf(\"输入参赛选手人数: \");\n scanf(\"%d\", &m);\n j = 2;\n for(i = 2; i < 8; i++){//判断是否整数次幂\n j = j * 2;\n if(j == m)\n break;\n }\n if(i >= 8){\n printf(\"参赛选手人数必须为2的整数次幂, 且不超过64个 \\n\");\n getch();\n return 0;\n }\n gamecal(1, m);\n printf(\"\\n编号 \");\n for(i = 2; i <= m; i++){\n printf(\"%2d天\", i - 1);\n }\n printf(\"\\n\");\n for(i = 1; i <= m; i++){\n for(j = 1; j <= m; j++){\n printf(\"%4d\", a[i][j]);\n }\n printf(\"\\n\");\n }\n \n getch();\n return 0;\n }\n","source":"_posts/20170807_fzf.md","raw":"---\ntitle: 分治法 (实例)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n\n## 使用分治法解决循环日程表问题\n\n> 要求: 安排比赛日程 每个选手与其他每个选手进行一对一比赛, 一天只与一个选手比赛,不重复比赛,不漏掉;\n\n\n\n\n\n> 例子: 如下图\n \n{% qnimg fzf1.jpg title:分治法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,\n 这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。\n 即一种分目标完成程序算法,简单问题可用二分法完成。\n\n> 分解复杂问题为简单的组合, 如图\n\n{% qnimg fzf2.jpg title:分治法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n \n #include \n #include\n #define MAXN 64\n int a[MAXN + 1][MAXN + 1] = {0}; //从1开始\n \n // 分治算法\n \n void gamecal(int k, int n) {//k表示开始的选手编号, n表示选手个数\n int i, j;\n if(n == 2){\n a[k][1] = k; // 参赛选手编号为k\n a[k][2] = k + 1; // 对阵选手编号为k+1\n a[k + 1][1] = k + 1; // 参赛选手编号为k\n a[k + 1][2] = k; // 对阵选手编号为k+1\n }else {\n gamecal(k, n/2);//递归完成左上角\n gamecal(k + n/2, n/2);//递归完成左下角\n \n //右上角,右下角 通过观察发现与左下角和左上角完全一样。可以通过循环求得\n for(i = k; i< k + n/2; i++){//右上角\n for(j = n/2 + 1; j <= n; j++){\n a[i][j] = a[i + n/2][j - n/2];\n }\n }\n for(i = k + n/2; i < k + n; i++){//右下角\n for(j = n/2 + 1; j <= n; j++){\n a[i][j] = a[i - n/2][j - n/2];\n }\n }\n \n }\n }\n \n int main(){\n int m,i,j;\n printf(\"输入参赛选手人数: \");\n scanf(\"%d\", &m);\n j = 2;\n for(i = 2; i < 8; i++){//判断是否整数次幂\n j = j * 2;\n if(j == m)\n break;\n }\n if(i >= 8){\n printf(\"参赛选手人数必须为2的整数次幂, 且不超过64个 \\n\");\n getch();\n return 0;\n }\n gamecal(1, m);\n printf(\"\\n编号 \");\n for(i = 2; i <= m; i++){\n printf(\"%2d天\", i - 1);\n }\n printf(\"\\n\");\n for(i = 1; i <= m; i++){\n for(j = 1; j <= m; j++){\n printf(\"%4d\", a[i][j]);\n }\n printf(\"\\n\");\n }\n \n getch();\n return 0;\n }\n","slug":"20170807_fzf","published":1,"date":"2020-01-15T05:50:39.617Z","updated":"2018-01-13T02:29:22.170Z","layout":"post","photos":[],"link":"","_id":"ckm3inuv0000q24ujmu8dohy0","content":"

使用分治法解决循环日程表问题

\n

要求: 安排比赛日程 每个选手与其他每个选手进行一对一比赛, 一天只与一个选手比赛,不重复比赛,不漏掉;

\n
\n\n
\n

例子: 如下图

\n
\n\"图片说明\"\n
分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,\n这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。\n即一种分目标完成程序算法,简单问题可用二分法完成。\n
\n

分解复杂问题为简单的组合, 如图

\n
\n\"图片说明\"\n
#include <stdio.h>\n#include<conio.h>\n#define MAXN 64\nint a[MAXN + 1][MAXN + 1] = {0}; //从1开始\n\n// 分治算法\n\nvoid gamecal(int k, int n) {//k表示开始的选手编号, n表示选手个数\n    int i, j;\n    if(n == 2){\n        a[k][1] = k; // 参赛选手编号为k\n        a[k][2] = k + 1; // 对阵选手编号为k+1\n        a[k + 1][1] = k + 1; // 参赛选手编号为k\n        a[k + 1][2] = k; // 对阵选手编号为k+1\n    }else {\n        gamecal(k, n/2);//递归完成左上角\n        gamecal(k + n/2, n/2);//递归完成左下角\n\n        //右上角,右下角 通过观察发现与左下角和左上角完全一样。可以通过循环求得\n        for(i = k; i< k + n/2; i++){//右上角\n            for(j = n/2 + 1; j <= n; j++){\n                a[i][j] = a[i + n/2][j - n/2];\n            }\n        }\n        for(i = k + n/2; i < k + n; i++){//右下角\n             for(j = n/2 + 1; j <= n; j++){\n                a[i][j] = a[i - n/2][j - n/2];\n            }\n        }\n\n    }\n}\n\nint main(){\n    int m,i,j;\n    printf("输入参赛选手人数: ");\n    scanf("%d", &m);\n    j = 2;\n    for(i = 2; i < 8; i++){//判断是否整数次幂\n        j = j * 2;\n        if(j == m)\n            break;\n    }\n    if(i >= 8){\n        printf("参赛选手人数必须为2的整数次幂, 且不超过64个 \\n");\n        getch();\n        return 0;\n    }\n    gamecal(1, m);\n    printf("\\n编号 ");\n    for(i = 2; i <= m; i++){\n        printf("%2d天", i - 1);\n    }\n    printf("\\n");\n    for(i = 1; i <= m; i++){\n        for(j = 1; j <= m; j++){\n            printf("%4d", a[i][j]);\n        }\n        printf("\\n");\n    }\n\n    getch();\n    return 0;\n}\n
","site":{"data":{}},"excerpt":"

使用分治法解决循环日程表问题

\n

要求: 安排比赛日程 每个选手与其他每个选手进行一对一比赛, 一天只与一个选手比赛,不重复比赛,不漏掉;

\n
","more":"
\n

例子: 如下图

\n
\n\"图片说明\"\n
分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,\n这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。\n即一种分目标完成程序算法,简单问题可用二分法完成。\n
\n

分解复杂问题为简单的组合, 如图

\n
\n\"图片说明\"\n
#include <stdio.h>\n#include<conio.h>\n#define MAXN 64\nint a[MAXN + 1][MAXN + 1] = {0}; //从1开始\n\n// 分治算法\n\nvoid gamecal(int k, int n) {//k表示开始的选手编号, n表示选手个数\n    int i, j;\n    if(n == 2){\n        a[k][1] = k; // 参赛选手编号为k\n        a[k][2] = k + 1; // 对阵选手编号为k+1\n        a[k + 1][1] = k + 1; // 参赛选手编号为k\n        a[k + 1][2] = k; // 对阵选手编号为k+1\n    }else {\n        gamecal(k, n/2);//递归完成左上角\n        gamecal(k + n/2, n/2);//递归完成左下角\n\n        //右上角,右下角 通过观察发现与左下角和左上角完全一样。可以通过循环求得\n        for(i = k; i< k + n/2; i++){//右上角\n            for(j = n/2 + 1; j <= n; j++){\n                a[i][j] = a[i + n/2][j - n/2];\n            }\n        }\n        for(i = k + n/2; i < k + n; i++){//右下角\n             for(j = n/2 + 1; j <= n; j++){\n                a[i][j] = a[i - n/2][j - n/2];\n            }\n        }\n\n    }\n}\n\nint main(){\n    int m,i,j;\n    printf("输入参赛选手人数: ");\n    scanf("%d", &m);\n    j = 2;\n    for(i = 2; i < 8; i++){//判断是否整数次幂\n        j = j * 2;\n        if(j == m)\n            break;\n    }\n    if(i >= 8){\n        printf("参赛选手人数必须为2的整数次幂, 且不超过64个 \\n");\n        getch();\n        return 0;\n    }\n    gamecal(1, m);\n    printf("\\n编号 ");\n    for(i = 2; i <= m; i++){\n        printf("%2d天", i - 1);\n    }\n    printf("\\n");\n    for(i = 1; i <= m; i++){\n        for(j = 1; j <= m; j++){\n            printf("%4d", a[i][j]);\n        }\n        printf("\\n");\n    }\n\n    getch();\n    return 0;\n}\n
"},{"title":"回溯算法 (实例)","comments":1,"description":null,"_content":"\n\n## 使用回溯算法解决N皇后问题\n\n> 回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。用回溯算法解决问题的一般步骤为:\n\n1. 定义一个解空间,它包含问题的解。\n2. 利用适于搜索的方法组织解空间。\n3. 利用深度优先法搜索解空间。\n4. 利用限界函数避免移动到不可能产生解的子空间。\n\n\n\n> 问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性\n\n> 要求:N皇后问题:在n*n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规矩,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n*n格的棋盘上方置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。我们需要求的是可放置的总数\n\n> 例子:如下图\n \n\n{% qnimg 2014217145358979.png title:回溯法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n \n #include \n #include \n #include\n // n表示在 n*n 的坐标中进行操作\n // 数组x[k]中 下表k表示横轴 x[k]得值表示纵轴\n static int n, x[1000];\n static long sum;\n \n //尝试 判断是否可以 如果不可以则回退到t+1层,再尝试其他的组合\n int Place(int k)\n {\n int j;\n for( j = 1;j < k; j++)//循环遍历已经确定位置的坐标(j表示行号) 与当前要选择的坐标进行对比\n if((abs(k - j) == abs(x[j] - x[k])) || (x[j] == x[k]))\n //三个条件 1.任意两个不能在同一个斜边上 2.不能在同一行 3.不能在同一列(即j==k)\n return 0;\n return 1;\n }\n \n void Backtrak(int k) //初始1\n {\n if(k > n)\n sum++;//如果超过最后一行就算成功一个\n else{\n int i;\n for( i=1; i <= n; i++){ // 循环一行的每个每一个空位,从1到n\n x[k] = i; // 表示第k行第i列\n if(Place(k)) // 判断\n Backtrak(k+1); // 操作完成 进行下一行\n }\n }\n }\n \n int main()\n {\n int nn;\n printf(\"请输入2的整数次幂的数: \");\n while(scanf(\"%d\",&nn)!=EOF){\n n=nn;\n sum=0;\n \n int i;\n for(i=0;i<=n;i++)\n x[i]=0;\n \n Backtrak(1);\n printf(\"%l\\n\",sum);\n }\n }\n \n","source":"_posts/20170807_hs.md","raw":"---\ntitle: 回溯算法 (实例)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n\n## 使用回溯算法解决N皇后问题\n\n> 回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。用回溯算法解决问题的一般步骤为:\n\n1. 定义一个解空间,它包含问题的解。\n2. 利用适于搜索的方法组织解空间。\n3. 利用深度优先法搜索解空间。\n4. 利用限界函数避免移动到不可能产生解的子空间。\n\n\n\n> 问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性\n\n> 要求:N皇后问题:在n*n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规矩,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n*n格的棋盘上方置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。我们需要求的是可放置的总数\n\n> 例子:如下图\n \n\n{% qnimg 2014217145358979.png title:回溯法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n \n #include \n #include \n #include\n // n表示在 n*n 的坐标中进行操作\n // 数组x[k]中 下表k表示横轴 x[k]得值表示纵轴\n static int n, x[1000];\n static long sum;\n \n //尝试 判断是否可以 如果不可以则回退到t+1层,再尝试其他的组合\n int Place(int k)\n {\n int j;\n for( j = 1;j < k; j++)//循环遍历已经确定位置的坐标(j表示行号) 与当前要选择的坐标进行对比\n if((abs(k - j) == abs(x[j] - x[k])) || (x[j] == x[k]))\n //三个条件 1.任意两个不能在同一个斜边上 2.不能在同一行 3.不能在同一列(即j==k)\n return 0;\n return 1;\n }\n \n void Backtrak(int k) //初始1\n {\n if(k > n)\n sum++;//如果超过最后一行就算成功一个\n else{\n int i;\n for( i=1; i <= n; i++){ // 循环一行的每个每一个空位,从1到n\n x[k] = i; // 表示第k行第i列\n if(Place(k)) // 判断\n Backtrak(k+1); // 操作完成 进行下一行\n }\n }\n }\n \n int main()\n {\n int nn;\n printf(\"请输入2的整数次幂的数: \");\n while(scanf(\"%d\",&nn)!=EOF){\n n=nn;\n sum=0;\n \n int i;\n for(i=0;i<=n;i++)\n x[i]=0;\n \n Backtrak(1);\n printf(\"%l\\n\",sum);\n }\n }\n \n","slug":"20170807_hs","published":1,"date":"2020-01-15T05:50:39.619Z","updated":"2018-01-13T02:29:22.171Z","layout":"post","photos":[],"link":"","_id":"ckm3inuv2000s24ujzz6ztk38","content":"

使用回溯算法解决N皇后问题

\n

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。用回溯算法解决问题的一般步骤为:

\n
\n
    \n
  1. 定义一个解空间,它包含问题的解。
  2. \n
  3. 利用适于搜索的方法组织解空间。
  4. \n
  5. 利用深度优先法搜索解空间。
  6. \n
  7. 利用限界函数避免移动到不可能产生解的子空间。
  8. \n
\n\n
\n

问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性

\n
\n
\n

要求:N皇后问题:在nn格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规矩,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在nn格的棋盘上方置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。我们需要求的是可放置的总数

\n
\n
\n

例子:如下图

\n
\n\"图片说明\"\n
#include <stdio.h>\n#include <math.h>\n#include<stdlib.h>\n// n表示在 n*n 的坐标中进行操作\n// 数组x[k]中 下表k表示横轴 x[k]得值表示纵轴\nstatic int n, x[1000];\nstatic long sum;\n\n//尝试 判断是否可以 如果不可以则回退到t+1层,再尝试其他的组合\nint Place(int k)\n{\n    int j;\n    for( j = 1;j < k; j++)//循环遍历已经确定位置的坐标(j表示行号) 与当前要选择的坐标进行对比\n        if((abs(k - j) == abs(x[j] - x[k])) || (x[j] == x[k]))\n            //三个条件 1.任意两个不能在同一个斜边上 2.不能在同一行 3.不能在同一列(即j==k)\n            return 0;\n     return 1;\n  }\n\nvoid Backtrak(int k) //初始1\n{\n   if(k > n)\n        sum++;//如果超过最后一行就算成功一个\n   else{\n        int i;\n        for( i=1; i <= n; i++){ // 循环一行的每个每一个空位,从1到n\n            x[k] = i; // 表示第k行第i列\n            if(Place(k)) // 判断\n                Backtrak(k+1); // 操作完成 进行下一行\n        }\n   }\n}\n\nint main()\n{\n    int nn;\n    printf("请输入2的整数次幂的数: ");\n    while(scanf("%d",&nn)!=EOF){\n        n=nn;\n        sum=0;\n\n        int i;\n        for(i=0;i<=n;i++)\n            x[i]=0;\n\n        Backtrak(1);\n        printf("%l\\n",sum);\n    }\n}\n
","site":{"data":{}},"excerpt":"

使用回溯算法解决N皇后问题

\n

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。用回溯算法解决问题的一般步骤为:

\n
\n
    \n
  1. 定义一个解空间,它包含问题的解。
  2. \n
  3. 利用适于搜索的方法组织解空间。
  4. \n
  5. 利用深度优先法搜索解空间。
  6. \n
  7. 利用限界函数避免移动到不可能产生解的子空间。
  8. \n
","more":"
\n

问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性

\n
\n
\n

要求:N皇后问题:在nn格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规矩,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在nn格的棋盘上方置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。我们需要求的是可放置的总数

\n
\n
\n

例子:如下图

\n
\n\"图片说明\"\n
#include <stdio.h>\n#include <math.h>\n#include<stdlib.h>\n// n表示在 n*n 的坐标中进行操作\n// 数组x[k]中 下表k表示横轴 x[k]得值表示纵轴\nstatic int n, x[1000];\nstatic long sum;\n\n//尝试 判断是否可以 如果不可以则回退到t+1层,再尝试其他的组合\nint Place(int k)\n{\n    int j;\n    for( j = 1;j < k; j++)//循环遍历已经确定位置的坐标(j表示行号) 与当前要选择的坐标进行对比\n        if((abs(k - j) == abs(x[j] - x[k])) || (x[j] == x[k]))\n            //三个条件 1.任意两个不能在同一个斜边上 2.不能在同一行 3.不能在同一列(即j==k)\n            return 0;\n     return 1;\n  }\n\nvoid Backtrak(int k) //初始1\n{\n   if(k > n)\n        sum++;//如果超过最后一行就算成功一个\n   else{\n        int i;\n        for( i=1; i <= n; i++){ // 循环一行的每个每一个空位,从1到n\n            x[k] = i; // 表示第k行第i列\n            if(Place(k)) // 判断\n                Backtrak(k+1); // 操作完成 进行下一行\n        }\n   }\n}\n\nint main()\n{\n    int nn;\n    printf("请输入2的整数次幂的数: ");\n    while(scanf("%d",&nn)!=EOF){\n        n=nn;\n        sum=0;\n\n        int i;\n        for(i=0;i<=n;i++)\n            x[i]=0;\n\n        Backtrak(1);\n        printf("%l\\n",sum);\n    }\n}\n
"},{"title":"模拟法 (实例)","comments":1,"description":null,"_content":"\n\n## 使用分治法解决猜数游戏\n\n\n 在程序设计语言中,\n 可使用随机函数来模拟自然界中发生的不可预测情况。C语言中使用srand()和rand()函数可生成随机数。\n\n\n\n \n \n #include \n #include \n \n int main()\n {\n int n, m, i = 0;\n srand(time(NULL));\n n = rand() % 100 + 1;\n \n do{\n printf(\"输入数字: \");\n scanf(\"%d\", &m);\n i++;\n \n if(m > n){\n printf(\"输入的数太大了\");\n }else if(m < n){\n printf(\"输入的数太小了\");\n }\n }while(m != n);\n \n printf(\"对了 猜了 %d 次\", i);\n \n return 0;\n }\n","source":"_posts/20170810_mn.md","raw":"---\ntitle: 模拟法 (实例)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n\n## 使用分治法解决猜数游戏\n\n\n 在程序设计语言中,\n 可使用随机函数来模拟自然界中发生的不可预测情况。C语言中使用srand()和rand()函数可生成随机数。\n\n\n\n \n \n #include \n #include \n \n int main()\n {\n int n, m, i = 0;\n srand(time(NULL));\n n = rand() % 100 + 1;\n \n do{\n printf(\"输入数字: \");\n scanf(\"%d\", &m);\n i++;\n \n if(m > n){\n printf(\"输入的数太大了\");\n }else if(m < n){\n printf(\"输入的数太小了\");\n }\n }while(m != n);\n \n printf(\"对了 猜了 %d 次\", i);\n \n return 0;\n }\n","slug":"20170810_mn","published":1,"date":"2020-01-15T05:50:39.621Z","updated":"2018-01-13T02:29:22.175Z","layout":"post","photos":[],"link":"","_id":"ckm3inuv5000x24ujyzlt7p4k","content":"

使用分治法解决猜数游戏

在程序设计语言中,\n可使用随机函数来模拟自然界中发生的不可预测情况。C语言中使用srand()和rand()函数可生成随机数。\n
\n
#include <stdio.h>\n#include <time.h>\n\nint main()\n{\n    int n, m, i = 0;\n    srand(time(NULL));\n    n = rand() % 100 + 1;\n\n    do{\n        printf("输入数字: ");\n        scanf("%d", &m);\n        i++;\n\n        if(m > n){\n            printf("输入的数太大了");\n        }else if(m < n){\n            printf("输入的数太小了");\n        }\n    }while(m != n);\n\n    printf("对了 猜了 %d 次", i);\n\n    return 0;\n}\n
","site":{"data":{}},"excerpt":"

使用分治法解决猜数游戏

在程序设计语言中,\n可使用随机函数来模拟自然界中发生的不可预测情况。C语言中使用srand()和rand()函数可生成随机数。\n
","more":"
#include <stdio.h>\n#include <time.h>\n\nint main()\n{\n    int n, m, i = 0;\n    srand(time(NULL));\n    n = rand() % 100 + 1;\n\n    do{\n        printf("输入数字: ");\n        scanf("%d", &m);\n        i++;\n\n        if(m > n){\n            printf("输入的数太大了");\n        }else if(m < n){\n            printf("输入的数太小了");\n        }\n    }while(m != n);\n\n    printf("对了 猜了 %d 次", i);\n\n    return 0;\n}\n
"},{"title":"贪心算法 (实例)","comments":1,"description":null,"_content":"\n\n## 使用贪心算法解决购物找零问题\n\n> 要求: 输入要找零的金额,求得所需各种面值的纸币的个数\n\n> 定义: 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。\n 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。\n\n\n\n\n \n 所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。\n \n1. 不能保证得到的是最优解\n2. 不能用来求最大或者最小解的问题\n3. 只能求满足某些条件的可行解的范围\n \n> 分解复杂问题为简单的组合, 如图\n\n{% qnimg fzf2.jpg title:分治法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n \n #include \n #include\n #define MAXN 9\n \n // 以分为单位 下面表示国内的9种面值的纸币\n int parvalue[MAXN] = {10000, 5000, 1000, 500, 200, 100, 50, 20, 10};\n \n //num数组存放需要每个面值纸币的个数(数组的9个元素表示对应的面值纸币的个数)\n int num[MAXN] = {0};\n \n int exchange(int n){\n int i, j;\n \n for(i = 0; i < MAXN; i++){ // 寻找比输入的钱低的最大面值\n if(n > parvalue[i]){\n break;\n }\n }\n \n while(n > 0 && i < MAXN){ //循环遍历数组(即从大到小对比面值和剩余的钱)\n if(n >= parvalue[i]){ // 剩余钱数大于当前的面值, 则需要该面值的纸币 该纸币个数+1 然后吧剩余钱数减去面值\n n -= parvalue[i];\n num[i]++;\n }else if(n < 10 && n >= 5){ // 如果钱数小于10分 则退出循环\n num[MAXN - 1]++;\n break;\n }else{ // 开始循环下一个面值的纸币\n i++;\n }\n }\n \n return 0;\n }\n \n int main(){\n int i;\n float m;\n printf(\"亲输入找零的金额 :\");\n scanf(\"%f\", &m);\n exchange((int)100 * m);\n printf(\"\\n %.2f元零钱的组成:\\n\", m);\n \n for(i = 0; i < MAXN; i++){\n if(num[i] > 0){\n printf(\"%6.2f: %d张\\n\", (float)parvalue[i] / 100.0, num[i]);\n }\n }\n \n getch();\n return 0;\n }\n","source":"_posts/20170807_tx.md","raw":"---\ntitle: 贪心算法 (实例)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n\n## 使用贪心算法解决购物找零问题\n\n> 要求: 输入要找零的金额,求得所需各种面值的纸币的个数\n\n> 定义: 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。\n 贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。\n\n\n\n\n \n 所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。\n \n1. 不能保证得到的是最优解\n2. 不能用来求最大或者最小解的问题\n3. 只能求满足某些条件的可行解的范围\n \n> 分解复杂问题为简单的组合, 如图\n\n{% qnimg fzf2.jpg title:分治法 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n \n #include \n #include\n #define MAXN 9\n \n // 以分为单位 下面表示国内的9种面值的纸币\n int parvalue[MAXN] = {10000, 5000, 1000, 500, 200, 100, 50, 20, 10};\n \n //num数组存放需要每个面值纸币的个数(数组的9个元素表示对应的面值纸币的个数)\n int num[MAXN] = {0};\n \n int exchange(int n){\n int i, j;\n \n for(i = 0; i < MAXN; i++){ // 寻找比输入的钱低的最大面值\n if(n > parvalue[i]){\n break;\n }\n }\n \n while(n > 0 && i < MAXN){ //循环遍历数组(即从大到小对比面值和剩余的钱)\n if(n >= parvalue[i]){ // 剩余钱数大于当前的面值, 则需要该面值的纸币 该纸币个数+1 然后吧剩余钱数减去面值\n n -= parvalue[i];\n num[i]++;\n }else if(n < 10 && n >= 5){ // 如果钱数小于10分 则退出循环\n num[MAXN - 1]++;\n break;\n }else{ // 开始循环下一个面值的纸币\n i++;\n }\n }\n \n return 0;\n }\n \n int main(){\n int i;\n float m;\n printf(\"亲输入找零的金额 :\");\n scanf(\"%f\", &m);\n exchange((int)100 * m);\n printf(\"\\n %.2f元零钱的组成:\\n\", m);\n \n for(i = 0; i < MAXN; i++){\n if(num[i] > 0){\n printf(\"%6.2f: %d张\\n\", (float)parvalue[i] / 100.0, num[i]);\n }\n }\n \n getch();\n return 0;\n }\n","slug":"20170807_tx","published":1,"date":"2020-01-15T05:50:39.620Z","updated":"2018-01-13T02:29:22.173Z","layout":"post","photos":[],"link":"","_id":"ckm3inuv6000z24ujxcrupu6m","content":"

使用贪心算法解决购物找零问题

\n

要求: 输入要找零的金额,求得所需各种面值的纸币的个数

\n
\n
\n

定义: 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

\n
\n\n
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。\n
    \n
  1. 不能保证得到的是最优解
  2. \n
  3. 不能用来求最大或者最小解的问题
  4. \n
  5. 只能求满足某些条件的可行解的范围
  6. \n
\n
\n

分解复杂问题为简单的组合, 如图

\n
\n\"图片说明\"\n
#include <stdio.h>\n#include<conio.h>\n#define MAXN 9\n\n// 以分为单位 下面表示国内的9种面值的纸币\nint parvalue[MAXN] = {10000, 5000, 1000, 500, 200, 100, 50, 20, 10};\n\n//num数组存放需要每个面值纸币的个数(数组的9个元素表示对应的面值纸币的个数)\nint num[MAXN] = {0};\n\nint exchange(int n){\n    int i, j;\n\n    for(i = 0; i < MAXN; i++){ // 寻找比输入的钱低的最大面值\n        if(n > parvalue[i]){\n            break;\n        }\n    }\n\n    while(n > 0 && i < MAXN){ //循环遍历数组(即从大到小对比面值和剩余的钱)\n        if(n >= parvalue[i]){ // 剩余钱数大于当前的面值, 则需要该面值的纸币 该纸币个数+1 然后吧剩余钱数减去面值\n            n -= parvalue[i];\n            num[i]++;\n        }else if(n < 10 && n >= 5){ // 如果钱数小于10分 则退出循环\n            num[MAXN - 1]++;\n            break;\n        }else{ // 开始循环下一个面值的纸币\n            i++;\n        }\n    }\n\n    return 0;\n}\n\nint main(){\n    int i;\n    float m;\n    printf("亲输入找零的金额 :");\n    scanf("%f", &m);\n    exchange((int)100 * m);\n    printf("\\n %.2f元零钱的组成:\\n", m);\n\n    for(i = 0; i < MAXN; i++){\n        if(num[i] > 0){\n            printf("%6.2f: %d张\\n", (float)parvalue[i] / 100.0, num[i]);\n        }\n    }\n\n    getch();\n    return 0;\n}\n
","site":{"data":{}},"excerpt":"

使用贪心算法解决购物找零问题

\n

要求: 输入要找零的金额,求得所需各种面值的纸币的个数

\n
\n
\n

定义: 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

\n
","more":"
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。\n
    \n
  1. 不能保证得到的是最优解
  2. \n
  3. 不能用来求最大或者最小解的问题
  4. \n
  5. 只能求满足某些条件的可行解的范围
  6. \n
\n
\n

分解复杂问题为简单的组合, 如图

\n
\n\"图片说明\"\n
#include <stdio.h>\n#include<conio.h>\n#define MAXN 9\n\n// 以分为单位 下面表示国内的9种面值的纸币\nint parvalue[MAXN] = {10000, 5000, 1000, 500, 200, 100, 50, 20, 10};\n\n//num数组存放需要每个面值纸币的个数(数组的9个元素表示对应的面值纸币的个数)\nint num[MAXN] = {0};\n\nint exchange(int n){\n    int i, j;\n\n    for(i = 0; i < MAXN; i++){ // 寻找比输入的钱低的最大面值\n        if(n > parvalue[i]){\n            break;\n        }\n    }\n\n    while(n > 0 && i < MAXN){ //循环遍历数组(即从大到小对比面值和剩余的钱)\n        if(n >= parvalue[i]){ // 剩余钱数大于当前的面值, 则需要该面值的纸币 该纸币个数+1 然后吧剩余钱数减去面值\n            n -= parvalue[i];\n            num[i]++;\n        }else if(n < 10 && n >= 5){ // 如果钱数小于10分 则退出循环\n            num[MAXN - 1]++;\n            break;\n        }else{ // 开始循环下一个面值的纸币\n            i++;\n        }\n    }\n\n    return 0;\n}\n\nint main(){\n    int i;\n    float m;\n    printf("亲输入找零的金额 :");\n    scanf("%f", &m);\n    exchange((int)100 * m);\n    printf("\\n %.2f元零钱的组成:\\n", m);\n\n    for(i = 0; i < MAXN; i++){\n        if(num[i] > 0){\n            printf("%6.2f: %d张\\n", (float)parvalue[i] / 100.0, num[i]);\n        }\n    }\n\n    getch();\n    return 0;\n}\n
"},{"title":"队列实例(全)","comments":1,"description":null,"_content":"\n> 队列:只允许在前端(队头)进行删除操作,在后端(队尾)进行插入操作,(先进先出)\n \n> 实现银行摇号排队共功能\n\n\n \n> 工程一共包含4个文件(由于代码不多,未分成头文件和实现)\n1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体\n2. SeqQueue.h :普通的队列以及一般功能的实现\n3. CycleQueue.h :循环队列以及功能的实现(实例)\n4. main.c : 测试文件\n\n\n 操作:1.初始化队列\n 2.进队\n 3.出队\n 4.获取长度\n 5.获取第一个元素\n\n\n> #### Entity.h\n\n typedef struct{\n int num; //顾客编号\n long time; //进入队列时间\n } DATA;\n\n\n> #### main.c\n\n #include \n #include \n #include \n \n #include \"CycleQueue.h\"\n int num;//顾客编号\n \n void add(CycQueue *q){//新增顾客排序\n DATA data;\n if(!CycQueueIsFull(q)){\n data.num = ++ num;\n data.time = time(NULL);\n CycQueueIn(q, data);\n }\n }\n \n void next(CycQueue *q){ //为当前顾客办理业务并通知下一个顾客准备\n DATA *data;\n if(!CycQueueIsEmpty(q)){\n data = CycQueueOut(q);\n printf(\"\\n请编号为%d的顾客办理业务\\n\",data->num);\n }\n if(!CycQueueIsEmpty(q)){\n data = CycQueuePeek(q);\n printf(\"请编号为%d的顾客准备办理\\n\", data->num);\n }\n }\n \n void CycQueueList(CycQueue *q){ //遍历当前队列\n int begin, end;\n begin = q->head;\n end = q->tail;\n if(!CycQueueIsEmpty(q)){\n while(1){\n if(begin != end){\n printf(\"-数组中实际位置(从0开始):%d;顾客编号:%d \\n\",begin, q->data[begin].num);\n begin = (begin +1) % QUEUEMAX;\n }else{\n break;\n }\n }\n }\n }\n \n int main()\n {\n CycQueue *queue1;\n int n;\n num = 0; //顾客编号\n queue1 = CycQueueInit();//初始化\n if(queue1 == NULL){\n printf(\"初始化队列失败\");\n return 0;\n }\n \n do{\n printf(\"\\n\\n\\n输入操作: 1.新顾客 2.办理业务 3.查看所有等待的顾客 0.退出\\n\");\n fflush(stdin);\n scanf(\"%d\", &n);\n switch(n){\n case 1:\n add(queue1);\n printf(\"当前有%d个顾客等待\\n\", CycQueueLen(queue1));\n break;\n case 2:\n next(queue1);\n printf(\"当前有%d个顾客等待\\n\", CycQueueLen(queue1));\n break;\n case 3:\n CycQueueList(queue1);\n break;\n case 0:\n break;\n }\n }while(1);\n \n return 0;\n }\n\n> #### CycleQueue.c\n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \n #include \"Entity.h\"\n #define QUEUEMAX 15 //设置队列最大容量\n \n typedef struct{\n DATA data[QUEUEMAX]; //队列数组\n int head;\n int tail;\n } CycQueue;\n \n CycQueue *CycQueueInit(){\n CycQueue *q;\n if(q = (CycQueue *)malloc(sizeof(CycQueue))){\n q->head = 0;\n q->tail = 0;\n \n return q;\n }else{\n return NULL;\n }\n }\n \n void CycQueueFree(CycQueue *q){ //释放队列内存\n if(q != NULL){\n free(q);\n }\n }\n \n int CycQueueIsEmpty(CycQueue *q){//判断队列是否为空\n return (q->head == q->tail);\n }\n \n int CycQueueIsFull(CycQueue *q){ //判断队列是否为满\n return ((q->tail+1) % QUEUEMAX == q->head);\n }\n \n int CycQueueLen(CycQueue *q){ //获取队列长度\n int n;\n n = q->tail - q->head;\n if(n < 0){\n n = QUEUEMAX + n;\n }\n return n;\n }\n \n /*入队*/\n int CycQueueIn(CycQueue *q, DATA data){\n if(CycQueueIsFull(q)){\n printf(\"队列已满!\");\n return 0;\n }else{\n q->data[q->tail] = data;\n q->tail = (q->tail+1) % QUEUEMAX;//tail++ 当tail到达最大值后就变1\n return 1;\n }\n }\n \n /*出队*/\n DATA *CycQueueOut(CycQueue *q){\n DATA * data;\n if(CycQueueIsEmpty(q)){\n printf(\"队列为空\");\n return 0;\n }else{\n data = &(q->data[q->head]);\n q->head = (q->head+1) % QUEUEMAX;\n return data;\n }\n }\n \n DATA *CycQueuePeek(CycQueue *q){//获取队列头部的元素\n if(CycQueueIsEmpty(q)){\n printf(\"队列为空\");\n return NULL;\n }else{\n return &(q->data[(q->head) % QUEUEMAX]);\n }\n }\n \n\n> #### SeqQueue.h \n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \n #include \"Entity.h\"\n #define QUEUEMAX 15 //设置队列最大容量\n \n typedef struct{\n DATA data[QUEUEMAX]; //队列数组\n int head;\n int tail;\n } SeqQueue;\n \n SeqQueue *SeqQueueInit(){\n SeqQueue *q;\n if(q = (SeqQueue *)malloc(sizeof(SeqQueue))){ // 申请保存队列的内存\n q->head = 0;\n q->tail = 0;\n \n return q;\n }else{\n return NULL;\n }\n }\n \n void SeqQueueFree(SeqQueue *q){ //释放队列内存\n if(q != NULL){\n free(q);\n }\n }\n \n int SeqQueueIsEmpty(SeqQueue *q){//判断队列是否为空\n return (q->head == q->tail);\n }\n \n int SeqQueueIsFull(SeqQueue *q){ //判断队列是否为满\n return (q->tail == QUEUEMAX);\n }\n \n int SeqQueueLen(SeqQueue *q){ //获取队列长度\n return (q->tail - q->head);\n }\n \n /*入队*/\n int SeqQueueIn(SeqQueue *q, DATA data){\n if(SeqQueueIsFull(q)){\n printf(\"队列已满!\");\n return 0;\n }else{\n q->data[q->tail++] = data;//先插入data数据到tail位置,然后tail++\n return 1;\n }\n }\n \n /*出队*/\n DATA *SeqQueueOut(SeqQueue *q){\n if(SeqQueueIsEmpty(q)){\n printf(\"队列为空\");\n return 0;\n }else{\n return &(q->data[q->head++]); //先得到data[head]的元素,然后head++\n }\n }\n \n DATA *SeqQueuePeek(SeqQueue *q){//获取队列头部的元素\n if(SeqQueueIsEmpty()){\n printf(\"队列为空\");\n return NULL;\n }else{\n return &(q->data[q->head]);\n }\n }\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170816_dl.md","raw":"---\ntitle: 队列实例(全)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> 队列:只允许在前端(队头)进行删除操作,在后端(队尾)进行插入操作,(先进先出)\n \n> 实现银行摇号排队共功能\n\n\n \n> 工程一共包含4个文件(由于代码不多,未分成头文件和实现)\n1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体\n2. SeqQueue.h :普通的队列以及一般功能的实现\n3. CycleQueue.h :循环队列以及功能的实现(实例)\n4. main.c : 测试文件\n\n\n 操作:1.初始化队列\n 2.进队\n 3.出队\n 4.获取长度\n 5.获取第一个元素\n\n\n> #### Entity.h\n\n typedef struct{\n int num; //顾客编号\n long time; //进入队列时间\n } DATA;\n\n\n> #### main.c\n\n #include \n #include \n #include \n \n #include \"CycleQueue.h\"\n int num;//顾客编号\n \n void add(CycQueue *q){//新增顾客排序\n DATA data;\n if(!CycQueueIsFull(q)){\n data.num = ++ num;\n data.time = time(NULL);\n CycQueueIn(q, data);\n }\n }\n \n void next(CycQueue *q){ //为当前顾客办理业务并通知下一个顾客准备\n DATA *data;\n if(!CycQueueIsEmpty(q)){\n data = CycQueueOut(q);\n printf(\"\\n请编号为%d的顾客办理业务\\n\",data->num);\n }\n if(!CycQueueIsEmpty(q)){\n data = CycQueuePeek(q);\n printf(\"请编号为%d的顾客准备办理\\n\", data->num);\n }\n }\n \n void CycQueueList(CycQueue *q){ //遍历当前队列\n int begin, end;\n begin = q->head;\n end = q->tail;\n if(!CycQueueIsEmpty(q)){\n while(1){\n if(begin != end){\n printf(\"-数组中实际位置(从0开始):%d;顾客编号:%d \\n\",begin, q->data[begin].num);\n begin = (begin +1) % QUEUEMAX;\n }else{\n break;\n }\n }\n }\n }\n \n int main()\n {\n CycQueue *queue1;\n int n;\n num = 0; //顾客编号\n queue1 = CycQueueInit();//初始化\n if(queue1 == NULL){\n printf(\"初始化队列失败\");\n return 0;\n }\n \n do{\n printf(\"\\n\\n\\n输入操作: 1.新顾客 2.办理业务 3.查看所有等待的顾客 0.退出\\n\");\n fflush(stdin);\n scanf(\"%d\", &n);\n switch(n){\n case 1:\n add(queue1);\n printf(\"当前有%d个顾客等待\\n\", CycQueueLen(queue1));\n break;\n case 2:\n next(queue1);\n printf(\"当前有%d个顾客等待\\n\", CycQueueLen(queue1));\n break;\n case 3:\n CycQueueList(queue1);\n break;\n case 0:\n break;\n }\n }while(1);\n \n return 0;\n }\n\n> #### CycleQueue.c\n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \n #include \"Entity.h\"\n #define QUEUEMAX 15 //设置队列最大容量\n \n typedef struct{\n DATA data[QUEUEMAX]; //队列数组\n int head;\n int tail;\n } CycQueue;\n \n CycQueue *CycQueueInit(){\n CycQueue *q;\n if(q = (CycQueue *)malloc(sizeof(CycQueue))){\n q->head = 0;\n q->tail = 0;\n \n return q;\n }else{\n return NULL;\n }\n }\n \n void CycQueueFree(CycQueue *q){ //释放队列内存\n if(q != NULL){\n free(q);\n }\n }\n \n int CycQueueIsEmpty(CycQueue *q){//判断队列是否为空\n return (q->head == q->tail);\n }\n \n int CycQueueIsFull(CycQueue *q){ //判断队列是否为满\n return ((q->tail+1) % QUEUEMAX == q->head);\n }\n \n int CycQueueLen(CycQueue *q){ //获取队列长度\n int n;\n n = q->tail - q->head;\n if(n < 0){\n n = QUEUEMAX + n;\n }\n return n;\n }\n \n /*入队*/\n int CycQueueIn(CycQueue *q, DATA data){\n if(CycQueueIsFull(q)){\n printf(\"队列已满!\");\n return 0;\n }else{\n q->data[q->tail] = data;\n q->tail = (q->tail+1) % QUEUEMAX;//tail++ 当tail到达最大值后就变1\n return 1;\n }\n }\n \n /*出队*/\n DATA *CycQueueOut(CycQueue *q){\n DATA * data;\n if(CycQueueIsEmpty(q)){\n printf(\"队列为空\");\n return 0;\n }else{\n data = &(q->data[q->head]);\n q->head = (q->head+1) % QUEUEMAX;\n return data;\n }\n }\n \n DATA *CycQueuePeek(CycQueue *q){//获取队列头部的元素\n if(CycQueueIsEmpty(q)){\n printf(\"队列为空\");\n return NULL;\n }else{\n return &(q->data[(q->head) % QUEUEMAX]);\n }\n }\n \n\n> #### SeqQueue.h \n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \n #include \"Entity.h\"\n #define QUEUEMAX 15 //设置队列最大容量\n \n typedef struct{\n DATA data[QUEUEMAX]; //队列数组\n int head;\n int tail;\n } SeqQueue;\n \n SeqQueue *SeqQueueInit(){\n SeqQueue *q;\n if(q = (SeqQueue *)malloc(sizeof(SeqQueue))){ // 申请保存队列的内存\n q->head = 0;\n q->tail = 0;\n \n return q;\n }else{\n return NULL;\n }\n }\n \n void SeqQueueFree(SeqQueue *q){ //释放队列内存\n if(q != NULL){\n free(q);\n }\n }\n \n int SeqQueueIsEmpty(SeqQueue *q){//判断队列是否为空\n return (q->head == q->tail);\n }\n \n int SeqQueueIsFull(SeqQueue *q){ //判断队列是否为满\n return (q->tail == QUEUEMAX);\n }\n \n int SeqQueueLen(SeqQueue *q){ //获取队列长度\n return (q->tail - q->head);\n }\n \n /*入队*/\n int SeqQueueIn(SeqQueue *q, DATA data){\n if(SeqQueueIsFull(q)){\n printf(\"队列已满!\");\n return 0;\n }else{\n q->data[q->tail++] = data;//先插入data数据到tail位置,然后tail++\n return 1;\n }\n }\n \n /*出队*/\n DATA *SeqQueueOut(SeqQueue *q){\n if(SeqQueueIsEmpty(q)){\n printf(\"队列为空\");\n return 0;\n }else{\n return &(q->data[q->head++]); //先得到data[head]的元素,然后head++\n }\n }\n \n DATA *SeqQueuePeek(SeqQueue *q){//获取队列头部的元素\n if(SeqQueueIsEmpty()){\n printf(\"队列为空\");\n return NULL;\n }else{\n return &(q->data[q->head]);\n }\n }\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170816_dl","published":1,"date":"2020-01-15T05:50:39.626Z","updated":"2021-03-10T13:50:15.242Z","layout":"post","photos":[],"link":"","_id":"ckm3inuv8001324ujisbmwrsk","content":"
\n

队列:只允许在前端(队头)进行删除操作,在后端(队尾)进行插入操作,(先进先出)

\n
\n
\n

实现银行摇号排队共功能

\n
\n\n
\n

工程一共包含4个文件(由于代码不多,未分成头文件和实现)

\n
    \n
  1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体
  2. \n
  3. SeqQueue.h :普通的队列以及一般功能的实现
  4. \n
  5. CycleQueue.h :循环队列以及功能的实现(实例)
  6. \n
  7. main.c : 测试文件
  8. \n
\n
\n
操作:1.初始化队列\n          2.进队\n          3.出队\n          4.获取长度\n          5.获取第一个元素\n
\n

Entity.h

\n
typedef struct{\n    int num;    //顾客编号\n    long time;  //进入队列时间\n} DATA;\n
\n

main.c

\n
#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include "CycleQueue.h"\nint num;//顾客编号\n\nvoid add(CycQueue *q){//新增顾客排序\n    DATA data;\n    if(!CycQueueIsFull(q)){\n        data.num = ++ num;\n        data.time = time(NULL);\n        CycQueueIn(q, data);\n    }\n}\n\nvoid next(CycQueue *q){ //为当前顾客办理业务并通知下一个顾客准备\n    DATA *data;\n    if(!CycQueueIsEmpty(q)){\n        data = CycQueueOut(q);\n        printf("\\n请编号为%d的顾客办理业务\\n",data->num);\n    }\n    if(!CycQueueIsEmpty(q)){\n        data = CycQueuePeek(q);\n        printf("请编号为%d的顾客准备办理\\n", data->num);\n    }\n}\n\nvoid CycQueueList(CycQueue *q){ //遍历当前队列\n    int begin, end;\n    begin = q->head;\n    end = q->tail;\n    if(!CycQueueIsEmpty(q)){\n        while(1){\n            if(begin != end){\n                printf("-数组中实际位置(从0开始):%d;顾客编号:%d \\n",begin, q->data[begin].num);\n                begin = (begin +1) % QUEUEMAX;\n            }else{\n                break;\n            }\n        }\n    }\n}\n\nint main()\n{\n    CycQueue *queue1;\n    int  n;\n    num = 0; //顾客编号\n    queue1 = CycQueueInit();//初始化\n    if(queue1 == NULL){\n        printf("初始化队列失败");\n        return 0;\n    }\n\n    do{\n        printf("\\n\\n\\n输入操作: 1.新顾客 2.办理业务 3.查看所有等待的顾客 0.退出\\n");\n        fflush(stdin);\n        scanf("%d", &n);\n        switch(n){\n            case 1:\n                add(queue1);\n                printf("当前有%d个顾客等待\\n", CycQueueLen(queue1));\n                break;\n            case 2:\n                next(queue1);\n                printf("当前有%d个顾客等待\\n", CycQueueLen(queue1));\n                break;\n            case 3:\n                CycQueueList(queue1);\n                break;\n            case 0:\n                break;\n        }\n    }while(1);\n\n    return 0;\n}\n
\n

CycleQueue.c

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include <malloc.h>\n#include "Entity.h"\n#define QUEUEMAX 15 //设置队列最大容量\n\ntypedef struct{\n    DATA data[QUEUEMAX]; //队列数组\n    int head;\n    int tail;\n} CycQueue;\n\nCycQueue *CycQueueInit(){\n    CycQueue *q;\n    if(q = (CycQueue *)malloc(sizeof(CycQueue))){\n        q->head = 0;\n        q->tail = 0;\n\n        return q;\n    }else{\n        return NULL;\n    }\n}\n\nvoid CycQueueFree(CycQueue *q){ //释放队列内存\n    if(q != NULL){\n        free(q);\n    }\n}\n\nint CycQueueIsEmpty(CycQueue *q){//判断队列是否为空\n    return (q->head == q->tail);\n}\n\nint CycQueueIsFull(CycQueue *q){ //判断队列是否为满\n    return ((q->tail+1) % QUEUEMAX == q->head);\n}\n\nint CycQueueLen(CycQueue *q){ //获取队列长度\n    int n;\n    n = q->tail - q->head;\n    if(n < 0){\n        n = QUEUEMAX + n;\n    }\n    return n;\n}\n\n/*入队*/\nint CycQueueIn(CycQueue *q, DATA data){\n    if(CycQueueIsFull(q)){\n        printf("队列已满!");\n        return 0;\n    }else{\n        q->data[q->tail] = data;\n        q->tail = (q->tail+1) % QUEUEMAX;//tail++ 当tail到达最大值后就变1\n        return 1;\n    }\n}\n\n/*出队*/\nDATA *CycQueueOut(CycQueue *q){\n    DATA * data;\n    if(CycQueueIsEmpty(q)){\n        printf("队列为空");\n        return 0;\n    }else{\n        data = &(q->data[q->head]);\n        q->head = (q->head+1) % QUEUEMAX;\n        return data;\n    }\n}\n\nDATA *CycQueuePeek(CycQueue *q){//获取队列头部的元素\n    if(CycQueueIsEmpty(q)){\n        printf("队列为空");\n        return NULL;\n    }else{\n        return &(q->data[(q->head) % QUEUEMAX]);\n    }\n}\n
\n

SeqQueue.h

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include <malloc.h>\n#include "Entity.h"\n#define QUEUEMAX 15 //设置队列最大容量\n\ntypedef struct{\n    DATA data[QUEUEMAX]; //队列数组\n    int head;\n    int tail;\n} SeqQueue;\n\nSeqQueue *SeqQueueInit(){\n    SeqQueue *q;\n    if(q = (SeqQueue *)malloc(sizeof(SeqQueue))){ // 申请保存队列的内存\n        q->head = 0;\n        q->tail = 0;\n\n        return q;\n    }else{\n        return NULL;\n    }\n}\n\nvoid SeqQueueFree(SeqQueue *q){ //释放队列内存\n    if(q != NULL){\n        free(q);\n    }\n}\n\nint SeqQueueIsEmpty(SeqQueue *q){//判断队列是否为空\n    return (q->head == q->tail);\n}\n\nint SeqQueueIsFull(SeqQueue *q){ //判断队列是否为满\n    return (q->tail == QUEUEMAX);\n}\n\nint SeqQueueLen(SeqQueue *q){ //获取队列长度\n    return (q->tail - q->head);\n}\n\n/*入队*/\nint SeqQueueIn(SeqQueue *q, DATA data){\n    if(SeqQueueIsFull(q)){\n        printf("队列已满!");\n        return 0;\n    }else{\n        q->data[q->tail++] = data;//先插入data数据到tail位置,然后tail++\n        return 1;\n    }\n}\n\n/*出队*/\nDATA *SeqQueueOut(SeqQueue *q){\n    if(SeqQueueIsEmpty(q)){\n        printf("队列为空");\n        return 0;\n    }else{\n        return &(q->data[q->head++]); //先得到data[head]的元素,然后head++\n    }\n}\n\nDATA *SeqQueuePeek(SeqQueue *q){//获取队列头部的元素\n    if(SeqQueueIsEmpty()){\n        printf("队列为空");\n        return NULL;\n    }else{\n        return &(q->data[q->head]);\n    }\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

队列:只允许在前端(队头)进行删除操作,在后端(队尾)进行插入操作,(先进先出)

\n
\n
\n

实现银行摇号排队共功能

\n
","more":"
\n

工程一共包含4个文件(由于代码不多,未分成头文件和实现)

\n
    \n
  1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体
  2. \n
  3. SeqQueue.h :普通的队列以及一般功能的实现
  4. \n
  5. CycleQueue.h :循环队列以及功能的实现(实例)
  6. \n
  7. main.c : 测试文件
  8. \n
\n
\n
操作:1.初始化队列\n          2.进队\n          3.出队\n          4.获取长度\n          5.获取第一个元素\n
\n

Entity.h

\n
typedef struct{\n    int num;    //顾客编号\n    long time;  //进入队列时间\n} DATA;\n
\n

main.c

\n
#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#include "CycleQueue.h"\nint num;//顾客编号\n\nvoid add(CycQueue *q){//新增顾客排序\n    DATA data;\n    if(!CycQueueIsFull(q)){\n        data.num = ++ num;\n        data.time = time(NULL);\n        CycQueueIn(q, data);\n    }\n}\n\nvoid next(CycQueue *q){ //为当前顾客办理业务并通知下一个顾客准备\n    DATA *data;\n    if(!CycQueueIsEmpty(q)){\n        data = CycQueueOut(q);\n        printf("\\n请编号为%d的顾客办理业务\\n",data->num);\n    }\n    if(!CycQueueIsEmpty(q)){\n        data = CycQueuePeek(q);\n        printf("请编号为%d的顾客准备办理\\n", data->num);\n    }\n}\n\nvoid CycQueueList(CycQueue *q){ //遍历当前队列\n    int begin, end;\n    begin = q->head;\n    end = q->tail;\n    if(!CycQueueIsEmpty(q)){\n        while(1){\n            if(begin != end){\n                printf("-数组中实际位置(从0开始):%d;顾客编号:%d \\n",begin, q->data[begin].num);\n                begin = (begin +1) % QUEUEMAX;\n            }else{\n                break;\n            }\n        }\n    }\n}\n\nint main()\n{\n    CycQueue *queue1;\n    int  n;\n    num = 0; //顾客编号\n    queue1 = CycQueueInit();//初始化\n    if(queue1 == NULL){\n        printf("初始化队列失败");\n        return 0;\n    }\n\n    do{\n        printf("\\n\\n\\n输入操作: 1.新顾客 2.办理业务 3.查看所有等待的顾客 0.退出\\n");\n        fflush(stdin);\n        scanf("%d", &n);\n        switch(n){\n            case 1:\n                add(queue1);\n                printf("当前有%d个顾客等待\\n", CycQueueLen(queue1));\n                break;\n            case 2:\n                next(queue1);\n                printf("当前有%d个顾客等待\\n", CycQueueLen(queue1));\n                break;\n            case 3:\n                CycQueueList(queue1);\n                break;\n            case 0:\n                break;\n        }\n    }while(1);\n\n    return 0;\n}\n
\n

CycleQueue.c

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include <malloc.h>\n#include "Entity.h"\n#define QUEUEMAX 15 //设置队列最大容量\n\ntypedef struct{\n    DATA data[QUEUEMAX]; //队列数组\n    int head;\n    int tail;\n} CycQueue;\n\nCycQueue *CycQueueInit(){\n    CycQueue *q;\n    if(q = (CycQueue *)malloc(sizeof(CycQueue))){\n        q->head = 0;\n        q->tail = 0;\n\n        return q;\n    }else{\n        return NULL;\n    }\n}\n\nvoid CycQueueFree(CycQueue *q){ //释放队列内存\n    if(q != NULL){\n        free(q);\n    }\n}\n\nint CycQueueIsEmpty(CycQueue *q){//判断队列是否为空\n    return (q->head == q->tail);\n}\n\nint CycQueueIsFull(CycQueue *q){ //判断队列是否为满\n    return ((q->tail+1) % QUEUEMAX == q->head);\n}\n\nint CycQueueLen(CycQueue *q){ //获取队列长度\n    int n;\n    n = q->tail - q->head;\n    if(n < 0){\n        n = QUEUEMAX + n;\n    }\n    return n;\n}\n\n/*入队*/\nint CycQueueIn(CycQueue *q, DATA data){\n    if(CycQueueIsFull(q)){\n        printf("队列已满!");\n        return 0;\n    }else{\n        q->data[q->tail] = data;\n        q->tail = (q->tail+1) % QUEUEMAX;//tail++ 当tail到达最大值后就变1\n        return 1;\n    }\n}\n\n/*出队*/\nDATA *CycQueueOut(CycQueue *q){\n    DATA * data;\n    if(CycQueueIsEmpty(q)){\n        printf("队列为空");\n        return 0;\n    }else{\n        data = &(q->data[q->head]);\n        q->head = (q->head+1) % QUEUEMAX;\n        return data;\n    }\n}\n\nDATA *CycQueuePeek(CycQueue *q){//获取队列头部的元素\n    if(CycQueueIsEmpty(q)){\n        printf("队列为空");\n        return NULL;\n    }else{\n        return &(q->data[(q->head) % QUEUEMAX]);\n    }\n}\n
\n

SeqQueue.h

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include <malloc.h>\n#include "Entity.h"\n#define QUEUEMAX 15 //设置队列最大容量\n\ntypedef struct{\n    DATA data[QUEUEMAX]; //队列数组\n    int head;\n    int tail;\n} SeqQueue;\n\nSeqQueue *SeqQueueInit(){\n    SeqQueue *q;\n    if(q = (SeqQueue *)malloc(sizeof(SeqQueue))){ // 申请保存队列的内存\n        q->head = 0;\n        q->tail = 0;\n\n        return q;\n    }else{\n        return NULL;\n    }\n}\n\nvoid SeqQueueFree(SeqQueue *q){ //释放队列内存\n    if(q != NULL){\n        free(q);\n    }\n}\n\nint SeqQueueIsEmpty(SeqQueue *q){//判断队列是否为空\n    return (q->head == q->tail);\n}\n\nint SeqQueueIsFull(SeqQueue *q){ //判断队列是否为满\n    return (q->tail == QUEUEMAX);\n}\n\nint SeqQueueLen(SeqQueue *q){ //获取队列长度\n    return (q->tail - q->head);\n}\n\n/*入队*/\nint SeqQueueIn(SeqQueue *q, DATA data){\n    if(SeqQueueIsFull(q)){\n        printf("队列已满!");\n        return 0;\n    }else{\n        q->data[q->tail++] = data;//先插入data数据到tail位置,然后tail++\n        return 1;\n    }\n}\n\n/*出队*/\nDATA *SeqQueueOut(SeqQueue *q){\n    if(SeqQueueIsEmpty(q)){\n        printf("队列为空");\n        return 0;\n    }else{\n        return &(q->data[q->head++]); //先得到data[head]的元素,然后head++\n    }\n}\n\nDATA *SeqQueuePeek(SeqQueue *q){//获取队列头部的元素\n    if(SeqQueueIsEmpty()){\n        printf("队列为空");\n        return NULL;\n    }else{\n        return &(q->data[q->head]);\n    }\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"线索二叉树","comments":1,"description":null,"_content":"\n> 线索二叉树:二叉树存储结构完全依靠链表,而二叉树的链表表示的关系是父结点和子结点(子树)的关系,\n 而无法直接获得前驱-后继的关系,即,当要求某个结点的前驱结点or后继结点比较麻烦;\n 为了在不增加指针的情况下直接快速找到前驱/后继结点,可以使用 线索二叉树 来实现;\n \n \n\n\n> 由于遍历方法不同时,产生的元素顺序不同,则每个元素的前驱/后继结点也不一定相同,\n 所以线索二叉树就由此分为先序线索二叉树,中序线索二叉树和后序线索二叉树;\n\n{% qnimg tree005.png title:中序线索二叉树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n 上图为 中序线索二叉树,由上图二叉树结构可知,其中序遍历结果为 B F D A C G E H 其中 B、D、F、C等元素拥有空的指针域;\n 这些空的指针域可以用来存放前驱或者后继结点的地址,这种指针称为线索(Thread);\n 而为了与存放子树(子树)的指针区分开来,增加了两个标志lflag,rflag表示左/右指针是哪种指针;\n 由此可得 线索二叉树 结构为:\n \n```c \ntypedef char DATA; //定义树结点的元素类型\ntypedef enum{\n SubTree,\n Thread\n}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1\n\ntypedef struct ChainTree{ //定义二叉树结点类型\n DATA data; //结点数据\n NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针\n NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针\n struct ChainTree *left; //左子树结点指针\n struct ChainTree *right;//右子树结点指针\n}ChainBinTree;\n```\n\n### 线索二叉树实例代码\n\n*本实例是在二叉树代码的基础上增加和修改的 这里只展示新增和修改的部分, 原来的代码见: http://zj2626.github.io/2017/08/27/20170827_ecs/*\n\n> #### BinTree.h\n\n```c\n/*\n 修改结构体为:\n*/\n\ntypedef char DATA; //定义树结点的元素类型\ntypedef enum{\n SubTree,\n Thread\n}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1\n\ntypedef struct ChainTree{ //定义二叉树结点类型\n DATA data; //结点数据\n NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针\n NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针\n struct ChainTree *left; //左子树结点指针\n struct ChainTree *right;//右子树结点指针\n}ChainBinTree;\n\n\n/*\n 增加的函数\n*/\n\n/*二叉树按中序线索化*/\nvoid BinTreeThreading_LDR(ChainBinTree *bt);\n\n/*中序线索二叉树 找到后继结点*/\nChainBinTree *BinTreeNext_LDR(ChainBinTree *bt);\n\n/*中序线索二叉树遍历*/\nvoid ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p));\n```\n \n> #### BinTree.c\n\n\n```c\n\n/*\n 增加的函数\n*/\n \n/********************************线索二叉树***************************/\n\nChainBinTree *Previous = NULL;//保存前驱结点指针\n\n\n/*二叉树按中序线索化*/\nvoid BinTreeThreading_LDR(ChainBinTree *bt){\n if(bt){//判断结点非空\n //左子树操作\n BinTreeThreading_LDR(bt->left); //递归调用,线索化左子树\n\n //当前结点操作\n bt->lflag = (bt->left) ? SubTree : Thread; //设置左指针域标志\n bt->rflag = (bt->right) ? SubTree : Thread; //设置右指针域标志\n if(Previous){ //判断前驱结点是否存在(就第一个没有前驱)\n if(Previous->rflag == Thread) //判断起前驱结点的右标志是否为线索,如果不是,则说明该前驱结点存在右子树\n Previous->right = bt; //设置前驱结点的右线索指向后继结点(当前)\n if(bt->lflag == Thread) //判断当前结点的左标志是否为线索,如果不是,则说明当前结点存在左子树\n bt->left = Previous; //设置当前结点的左线索指向前驱结点\n }\n Previous = bt;//保存刚访问的结点到Previous,作为下一个结点的前驱结点\n\n //右子树操作\n BinTreeThreading_LDR(bt->right);//递归调用,线索化右子树\n }\n}\n\n/*中序线索二叉树 找到后继结点*/\nChainBinTree *BinTreeNext_LDR(ChainBinTree *bt){\n ChainBinTree *nextNode; //存放后继结点\n if(!bt)\n return NULL;\n\n if(bt->rflag == Thread){ //判断当前结点右标志是否为线索,如果是则说明right存放的是后继结点的地址,直接返回\n return bt->right;\n }else{\n nextNode = bt->right; //暂时存放当前结点的右子树的根节点\n while(nextNode->lflag == SubTree){ //循环获取右子树的“最左结点”,这就是要求的后继结点\n nextNode = nextNode->left;\n }\n return nextNode;\n }\n}\n\n/*中序线索二叉树遍历*/\nvoid ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){\n if(bt){\n while(bt->lflag == SubTree){ //循环找到第一个中序遍历的结点\n bt = bt->left;\n }\n\n do{\n oper(bt);\n bt = BinTreeNext_LDR(bt); //获取后继结点,把地址赋值给bt\n }while(bt);\n }\n}\n```\n \n> #### main.c\n\n```cpp\n /*\n 修改的main函数为:\n */\n\n int main()\n {\n ChainBinTree *root = NULL; //root为指向二叉树根节点的指针\n char select;\n void (*oper1)(); //指向函数的指针\n oper1 = oper; //指向具体操作的函数\n do{\n printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 8.生成中序线索二叉树 9.遍历中序线索二叉树 0.退出\");\n select = getch();\n switch(select){\n case '1':\n root = initRoot();\n break;\n case '2':\n addNode(root);\n break;\n case '3':\n binTree_DLR(root, oper1);\n printf(\"\\n\");\n break;\n case '4':\n binTree_LDR(root, oper1);\n printf(\"\\n\");\n break;\n case '5':\n binTree_LRD(root, oper1);\n printf(\"\\n\");\n break;\n case '6':\n binTree_Level(root, oper1);\n printf(\"\\n\");\n break;\n case '7':\n printf(\"%d\", binTreeDepth(root));\n break;\n case '8':\n BinTreeThreading_LDR(root);\n break;\n case '9':\n ThreadBinTree_LDR(root, oper1);\n break;\n }\n }while(select != '0');\n \n binTreeClear(root);\n root = NULL;\n \n return 0;\n }\n```\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170906_xsecs.md","raw":"---\ntitle: 线索二叉树\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> 线索二叉树:二叉树存储结构完全依靠链表,而二叉树的链表表示的关系是父结点和子结点(子树)的关系,\n 而无法直接获得前驱-后继的关系,即,当要求某个结点的前驱结点or后继结点比较麻烦;\n 为了在不增加指针的情况下直接快速找到前驱/后继结点,可以使用 线索二叉树 来实现;\n \n \n\n\n> 由于遍历方法不同时,产生的元素顺序不同,则每个元素的前驱/后继结点也不一定相同,\n 所以线索二叉树就由此分为先序线索二叉树,中序线索二叉树和后序线索二叉树;\n\n{% qnimg tree005.png title:中序线索二叉树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n 上图为 中序线索二叉树,由上图二叉树结构可知,其中序遍历结果为 B F D A C G E H 其中 B、D、F、C等元素拥有空的指针域;\n 这些空的指针域可以用来存放前驱或者后继结点的地址,这种指针称为线索(Thread);\n 而为了与存放子树(子树)的指针区分开来,增加了两个标志lflag,rflag表示左/右指针是哪种指针;\n 由此可得 线索二叉树 结构为:\n \n```c \ntypedef char DATA; //定义树结点的元素类型\ntypedef enum{\n SubTree,\n Thread\n}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1\n\ntypedef struct ChainTree{ //定义二叉树结点类型\n DATA data; //结点数据\n NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针\n NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针\n struct ChainTree *left; //左子树结点指针\n struct ChainTree *right;//右子树结点指针\n}ChainBinTree;\n```\n\n### 线索二叉树实例代码\n\n*本实例是在二叉树代码的基础上增加和修改的 这里只展示新增和修改的部分, 原来的代码见: http://zj2626.github.io/2017/08/27/20170827_ecs/*\n\n> #### BinTree.h\n\n```c\n/*\n 修改结构体为:\n*/\n\ntypedef char DATA; //定义树结点的元素类型\ntypedef enum{\n SubTree,\n Thread\n}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1\n\ntypedef struct ChainTree{ //定义二叉树结点类型\n DATA data; //结点数据\n NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针\n NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针\n struct ChainTree *left; //左子树结点指针\n struct ChainTree *right;//右子树结点指针\n}ChainBinTree;\n\n\n/*\n 增加的函数\n*/\n\n/*二叉树按中序线索化*/\nvoid BinTreeThreading_LDR(ChainBinTree *bt);\n\n/*中序线索二叉树 找到后继结点*/\nChainBinTree *BinTreeNext_LDR(ChainBinTree *bt);\n\n/*中序线索二叉树遍历*/\nvoid ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p));\n```\n \n> #### BinTree.c\n\n\n```c\n\n/*\n 增加的函数\n*/\n \n/********************************线索二叉树***************************/\n\nChainBinTree *Previous = NULL;//保存前驱结点指针\n\n\n/*二叉树按中序线索化*/\nvoid BinTreeThreading_LDR(ChainBinTree *bt){\n if(bt){//判断结点非空\n //左子树操作\n BinTreeThreading_LDR(bt->left); //递归调用,线索化左子树\n\n //当前结点操作\n bt->lflag = (bt->left) ? SubTree : Thread; //设置左指针域标志\n bt->rflag = (bt->right) ? SubTree : Thread; //设置右指针域标志\n if(Previous){ //判断前驱结点是否存在(就第一个没有前驱)\n if(Previous->rflag == Thread) //判断起前驱结点的右标志是否为线索,如果不是,则说明该前驱结点存在右子树\n Previous->right = bt; //设置前驱结点的右线索指向后继结点(当前)\n if(bt->lflag == Thread) //判断当前结点的左标志是否为线索,如果不是,则说明当前结点存在左子树\n bt->left = Previous; //设置当前结点的左线索指向前驱结点\n }\n Previous = bt;//保存刚访问的结点到Previous,作为下一个结点的前驱结点\n\n //右子树操作\n BinTreeThreading_LDR(bt->right);//递归调用,线索化右子树\n }\n}\n\n/*中序线索二叉树 找到后继结点*/\nChainBinTree *BinTreeNext_LDR(ChainBinTree *bt){\n ChainBinTree *nextNode; //存放后继结点\n if(!bt)\n return NULL;\n\n if(bt->rflag == Thread){ //判断当前结点右标志是否为线索,如果是则说明right存放的是后继结点的地址,直接返回\n return bt->right;\n }else{\n nextNode = bt->right; //暂时存放当前结点的右子树的根节点\n while(nextNode->lflag == SubTree){ //循环获取右子树的“最左结点”,这就是要求的后继结点\n nextNode = nextNode->left;\n }\n return nextNode;\n }\n}\n\n/*中序线索二叉树遍历*/\nvoid ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){\n if(bt){\n while(bt->lflag == SubTree){ //循环找到第一个中序遍历的结点\n bt = bt->left;\n }\n\n do{\n oper(bt);\n bt = BinTreeNext_LDR(bt); //获取后继结点,把地址赋值给bt\n }while(bt);\n }\n}\n```\n \n> #### main.c\n\n```cpp\n /*\n 修改的main函数为:\n */\n\n int main()\n {\n ChainBinTree *root = NULL; //root为指向二叉树根节点的指针\n char select;\n void (*oper1)(); //指向函数的指针\n oper1 = oper; //指向具体操作的函数\n do{\n printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 8.生成中序线索二叉树 9.遍历中序线索二叉树 0.退出\");\n select = getch();\n switch(select){\n case '1':\n root = initRoot();\n break;\n case '2':\n addNode(root);\n break;\n case '3':\n binTree_DLR(root, oper1);\n printf(\"\\n\");\n break;\n case '4':\n binTree_LDR(root, oper1);\n printf(\"\\n\");\n break;\n case '5':\n binTree_LRD(root, oper1);\n printf(\"\\n\");\n break;\n case '6':\n binTree_Level(root, oper1);\n printf(\"\\n\");\n break;\n case '7':\n printf(\"%d\", binTreeDepth(root));\n break;\n case '8':\n BinTreeThreading_LDR(root);\n break;\n case '9':\n ThreadBinTree_LDR(root, oper1);\n break;\n }\n }while(select != '0');\n \n binTreeClear(root);\n root = NULL;\n \n return 0;\n }\n```\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170906_xsecs","published":1,"date":"2020-01-15T05:50:39.630Z","updated":"2021-03-10T13:50:15.372Z","layout":"post","photos":[],"link":"","_id":"ckm3inuv9001524ujla9lqoa7","content":"
\n

线索二叉树:二叉树存储结构完全依靠链表,而二叉树的链表表示的关系是父结点和子结点(子树)的关系,
而无法直接获得前驱-后继的关系,即,当要求某个结点的前驱结点or后继结点比较麻烦;
为了在不增加指针的情况下直接快速找到前驱/后继结点,可以使用 线索二叉树 来实现;

\n
\n\n
\n

由于遍历方法不同时,产生的元素顺序不同,则每个元素的前驱/后继结点也不一定相同,
所以线索二叉树就由此分为先序线索二叉树,中序线索二叉树和后序线索二叉树;

\n
\n\"图片说明\"\n
上图为 中序线索二叉树,由上图二叉树结构可知,其中序遍历结果为 B F D A C G E H 其中 B、D、F、C等元素拥有空的指针域;\n这些空的指针域可以用来存放前驱或者后继结点的地址,这种指针称为线索(Thread);\n而为了与存放子树(子树)的指针区分开来,增加了两个标志lflag,rflag表示左/右指针是哪种指针;\n由此可得 线索二叉树 结构为:\n
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef char DATA;          //定义树结点的元素类型
typedef enum{
SubTree,
Thread
}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1

typedef struct ChainTree{ //定义二叉树结点类型
DATA data; //结点数据
NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针
NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针
struct ChainTree *left; //左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainBinTree;
\n

线索二叉树实例代码

本实例是在二叉树代码的基础上增加和修改的 这里只展示新增和修改的部分, 原来的代码见: http://zj2626.github.io/2017/08/27/20170827_ecs/

\n
\n

BinTree.h

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
修改结构体为:
*/

typedef char DATA; //定义树结点的元素类型
typedef enum{
SubTree,
Thread
}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1

typedef struct ChainTree{ //定义二叉树结点类型
DATA data; //结点数据
NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针
NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针
struct ChainTree *left; //左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainBinTree;


/*
增加的函数
*/

/*二叉树按中序线索化*/
void BinTreeThreading_LDR(ChainBinTree *bt);

/*中序线索二叉树 找到后继结点*/
ChainBinTree *BinTreeNext_LDR(ChainBinTree *bt);

/*中序线索二叉树遍历*/
void ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p));
\n
\n

BinTree.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

/*
增加的函数
*/

/********************************线索二叉树***************************/

ChainBinTree *Previous = NULL;//保存前驱结点指针


/*二叉树按中序线索化*/
void BinTreeThreading_LDR(ChainBinTree *bt){
if(bt){//判断结点非空
//左子树操作
BinTreeThreading_LDR(bt->left); //递归调用,线索化左子树

//当前结点操作
bt->lflag = (bt->left) ? SubTree : Thread; //设置左指针域标志
bt->rflag = (bt->right) ? SubTree : Thread; //设置右指针域标志
if(Previous){ //判断前驱结点是否存在(就第一个没有前驱)
if(Previous->rflag == Thread) //判断起前驱结点的右标志是否为线索,如果不是,则说明该前驱结点存在右子树
Previous->right = bt; //设置前驱结点的右线索指向后继结点(当前)
if(bt->lflag == Thread) //判断当前结点的左标志是否为线索,如果不是,则说明当前结点存在左子树
bt->left = Previous; //设置当前结点的左线索指向前驱结点
}
Previous = bt;//保存刚访问的结点到Previous,作为下一个结点的前驱结点

//右子树操作
BinTreeThreading_LDR(bt->right);//递归调用,线索化右子树
}
}

/*中序线索二叉树 找到后继结点*/
ChainBinTree *BinTreeNext_LDR(ChainBinTree *bt){
ChainBinTree *nextNode; //存放后继结点
if(!bt)
return NULL;

if(bt->rflag == Thread){ //判断当前结点右标志是否为线索,如果是则说明right存放的是后继结点的地址,直接返回
return bt->right;
}else{
nextNode = bt->right; //暂时存放当前结点的右子树的根节点
while(nextNode->lflag == SubTree){ //循环获取右子树的“最左结点”,这就是要求的后继结点
nextNode = nextNode->left;
}
return nextNode;
}
}

/*中序线索二叉树遍历*/
void ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){
if(bt){
while(bt->lflag == SubTree){ //循环找到第一个中序遍历的结点
bt = bt->left;
}

do{
oper(bt);
bt = BinTreeNext_LDR(bt); //获取后继结点,把地址赋值给bt
}while(bt);
}
}
\n
\n

main.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
修改的main函数为:
*/

int main()
{
ChainBinTree *root = NULL; //root为指向二叉树根节点的指针
char select;
void (*oper1)(); //指向函数的指针
oper1 = oper; //指向具体操作的函数
do{
printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 8.生成中序线索二叉树 9.遍历中序线索二叉树 0.退出\");
select = getch();
switch(select){
case '1':
root = initRoot();
break;
case '2':
addNode(root);
break;
case '3':
binTree_DLR(root, oper1);
printf(\"\\n\");
break;
case '4':
binTree_LDR(root, oper1);
printf(\"\\n\");
break;
case '5':
binTree_LRD(root, oper1);
printf(\"\\n\");
break;
case '6':
binTree_Level(root, oper1);
printf(\"\\n\");
break;
case '7':
printf(\"%d\", binTreeDepth(root));
break;
case '8':
BinTreeThreading_LDR(root);
break;
case '9':
ThreadBinTree_LDR(root, oper1);
break;
}
}while(select != '0');

binTreeClear(root);
root = NULL;

return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

线索二叉树:二叉树存储结构完全依靠链表,而二叉树的链表表示的关系是父结点和子结点(子树)的关系,
而无法直接获得前驱-后继的关系,即,当要求某个结点的前驱结点or后继结点比较麻烦;
为了在不增加指针的情况下直接快速找到前驱/后继结点,可以使用 线索二叉树 来实现;

\n
","more":"
\n

由于遍历方法不同时,产生的元素顺序不同,则每个元素的前驱/后继结点也不一定相同,
所以线索二叉树就由此分为先序线索二叉树,中序线索二叉树和后序线索二叉树;

\n
\n\"图片说明\"\n
上图为 中序线索二叉树,由上图二叉树结构可知,其中序遍历结果为 B F D A C G E H 其中 B、D、F、C等元素拥有空的指针域;\n这些空的指针域可以用来存放前驱或者后继结点的地址,这种指针称为线索(Thread);\n而为了与存放子树(子树)的指针区分开来,增加了两个标志lflag,rflag表示左/右指针是哪种指针;\n由此可得 线索二叉树 结构为:\n
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef char DATA;          //定义树结点的元素类型
typedef enum{
SubTree,
Thread
}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1

typedef struct ChainTree{ //定义二叉树结点类型
DATA data; //结点数据
NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针
NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针
struct ChainTree *left; //左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainBinTree;
\n

线索二叉树实例代码

本实例是在二叉树代码的基础上增加和修改的 这里只展示新增和修改的部分, 原来的代码见: http://zj2626.github.io/2017/08/27/20170827_ecs/

\n
\n

BinTree.h

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
修改结构体为:
*/

typedef char DATA; //定义树结点的元素类型
typedef enum{
SubTree,
Thread
}NodeFlag; //定义枚举类型NodeFlag,包含SubTree(表示子树)和Thread(表示线索),分别为 0,1

typedef struct ChainTree{ //定义二叉树结点类型
DATA data; //结点数据
NodeFlag lflag; //左标志:用来表示左指针是子树指针还是线索指针
NodeFlag rflag; //右标志:用来表示右指针是子树指针还是线索指针
struct ChainTree *left; //左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainBinTree;


/*
增加的函数
*/

/*二叉树按中序线索化*/
void BinTreeThreading_LDR(ChainBinTree *bt);

/*中序线索二叉树 找到后继结点*/
ChainBinTree *BinTreeNext_LDR(ChainBinTree *bt);

/*中序线索二叉树遍历*/
void ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p));
\n
\n

BinTree.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

/*
增加的函数
*/

/********************************线索二叉树***************************/

ChainBinTree *Previous = NULL;//保存前驱结点指针


/*二叉树按中序线索化*/
void BinTreeThreading_LDR(ChainBinTree *bt){
if(bt){//判断结点非空
//左子树操作
BinTreeThreading_LDR(bt->left); //递归调用,线索化左子树

//当前结点操作
bt->lflag = (bt->left) ? SubTree : Thread; //设置左指针域标志
bt->rflag = (bt->right) ? SubTree : Thread; //设置右指针域标志
if(Previous){ //判断前驱结点是否存在(就第一个没有前驱)
if(Previous->rflag == Thread) //判断起前驱结点的右标志是否为线索,如果不是,则说明该前驱结点存在右子树
Previous->right = bt; //设置前驱结点的右线索指向后继结点(当前)
if(bt->lflag == Thread) //判断当前结点的左标志是否为线索,如果不是,则说明当前结点存在左子树
bt->left = Previous; //设置当前结点的左线索指向前驱结点
}
Previous = bt;//保存刚访问的结点到Previous,作为下一个结点的前驱结点

//右子树操作
BinTreeThreading_LDR(bt->right);//递归调用,线索化右子树
}
}

/*中序线索二叉树 找到后继结点*/
ChainBinTree *BinTreeNext_LDR(ChainBinTree *bt){
ChainBinTree *nextNode; //存放后继结点
if(!bt)
return NULL;

if(bt->rflag == Thread){ //判断当前结点右标志是否为线索,如果是则说明right存放的是后继结点的地址,直接返回
return bt->right;
}else{
nextNode = bt->right; //暂时存放当前结点的右子树的根节点
while(nextNode->lflag == SubTree){ //循环获取右子树的“最左结点”,这就是要求的后继结点
nextNode = nextNode->left;
}
return nextNode;
}
}

/*中序线索二叉树遍历*/
void ThreadBinTree_LDR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){
if(bt){
while(bt->lflag == SubTree){ //循环找到第一个中序遍历的结点
bt = bt->left;
}

do{
oper(bt);
bt = BinTreeNext_LDR(bt); //获取后继结点,把地址赋值给bt
}while(bt);
}
}
\n
\n

main.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
修改的main函数为:
*/

int main()
{
ChainBinTree *root = NULL; //root为指向二叉树根节点的指针
char select;
void (*oper1)(); //指向函数的指针
oper1 = oper; //指向具体操作的函数
do{
printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 8.生成中序线索二叉树 9.遍历中序线索二叉树 0.退出\");
select = getch();
switch(select){
case '1':
root = initRoot();
break;
case '2':
addNode(root);
break;
case '3':
binTree_DLR(root, oper1);
printf(\"\\n\");
break;
case '4':
binTree_LDR(root, oper1);
printf(\"\\n\");
break;
case '5':
binTree_LRD(root, oper1);
printf(\"\\n\");
break;
case '6':
binTree_Level(root, oper1);
printf(\"\\n\");
break;
case '7':
printf(\"%d\", binTreeDepth(root));
break;
case '8':
BinTreeThreading_LDR(root);
break;
case '9':
ThreadBinTree_LDR(root, oper1);
break;
}
}while(select != '0');

binTreeClear(root);
root = NULL;

return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"待完成","comments":1,"description":null,"_content":"http://www.cnblogs.com/lyajs/p/5779021.html\n \n \n\n package collection;\n \n import org.junit.Test;\n \n import java.util.ArrayList;\n import java.util.ArrayList;\n \n /**\n * Created by zj on 2017/9/11.\n */\n public class Link {\n \n @Test\n public void test() {\n ArrayList list = new ArrayList<>();\n //添加两个元素\n Student stJack = new Student(\"Jack\", \"AAAA\");\n Student stTom = new Student(\"Tom\", \"BBBBB\");\n list.add(stJack);\n list.add(stTom);\n //克隆\n ArrayList listCopy = new ArrayList<>();\n /*list.forEach(li -> {\n listCopy.add(li);\n });*/\n listCopy = (ArrayList) list.clone();\n \n listCopy.get(1).setId(\"FFFFFFFFFFF\");\n System.out.println(list);\n System.out.println(list.get(1).hashCode());\n System.out.println(listCopy);\n System.out.println(listCopy.get(1).hashCode());\n \n /*ArrayList ArrayList = new ArrayList<>();\n ArrayList.add(\"A\");\n ArrayList.add(\"B\");\n ArrayList.add(\"C\");\n ArrayList.add(\"D\");\n ArrayList.add(\"E\");\n \n System.out.println(ArrayList.clone());\n \n ArrayList.forEach(li -> {\n System.out.println(li);\n });*/\n }\n }\n \n \n package collection;\n \n import java.util.HashSet;\n import java.util.Set;\n \n /**\n * Created by zj on 2017/9/10.\n */\n public class Student {\n private String id;\n private String name;\n private Set courses;\n \n public Student() {\n }\n \n public Student(String id, String name) {\n this.id = id;\n this.name = name;\n this.courses = new HashSet();\n }\n \n public Student(String id, String name, Set courses) {\n this.id = id;\n this.name = name;\n this.courses = courses;\n }\n \n @Override\n public String toString() {\n return \"Student{\" +\n \"id='\" + id + '\\'' +\n \", name='\" + name + '\\'' +\n \", courses=\" + courses +\n '}';\n }\n \n @Override\n protected Object clone() throws CloneNotSupportedException {\n return new Student(this.id, this.name);\n }\n \n public String getId() {\n return id;\n }\n \n public void setId(String id) {\n this.id = id;\n }\n \n public String getName() {\n return name;\n }\n \n public void setName(String name) {\n this.name = name;\n }\n \n public Set getCourses() {\n return courses;\n }\n \n public void setCourses(Set courses) {\n this.courses = courses;\n }\n }\n\n\n public static void main(String[] args) {\n Map map = new HashMap();\n \n map.put(\"a\", new Student(\"A\", \"FFF\"));\n map.put(\"b\", new Student(\"B\", \"AAA\"));\n map.put(\"d\", new Student(\"D\", \"KKK\"));\n map.put(\"c\", new Student(\"C\", \"KKK\"));\n \n List> list =\n new ArrayList<>(map.entrySet());\n \n Collections.sort(list, new Comparator>() {\n @Override\n public int compare(Map.Entry o1, Map.Entry o2) {\n return o1.getValue().compareTo(o2.getValue());\n }\n });\n \n for (Map.Entry mapping : list) {\n System.out.println(mapping.getKey() + \":\" + mapping.getValue());\n }\n }\n\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170911_list_clone.md","raw":"---\ntitle: 待完成\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\nhttp://www.cnblogs.com/lyajs/p/5779021.html\n \n \n\n package collection;\n \n import org.junit.Test;\n \n import java.util.ArrayList;\n import java.util.ArrayList;\n \n /**\n * Created by zj on 2017/9/11.\n */\n public class Link {\n \n @Test\n public void test() {\n ArrayList list = new ArrayList<>();\n //添加两个元素\n Student stJack = new Student(\"Jack\", \"AAAA\");\n Student stTom = new Student(\"Tom\", \"BBBBB\");\n list.add(stJack);\n list.add(stTom);\n //克隆\n ArrayList listCopy = new ArrayList<>();\n /*list.forEach(li -> {\n listCopy.add(li);\n });*/\n listCopy = (ArrayList) list.clone();\n \n listCopy.get(1).setId(\"FFFFFFFFFFF\");\n System.out.println(list);\n System.out.println(list.get(1).hashCode());\n System.out.println(listCopy);\n System.out.println(listCopy.get(1).hashCode());\n \n /*ArrayList ArrayList = new ArrayList<>();\n ArrayList.add(\"A\");\n ArrayList.add(\"B\");\n ArrayList.add(\"C\");\n ArrayList.add(\"D\");\n ArrayList.add(\"E\");\n \n System.out.println(ArrayList.clone());\n \n ArrayList.forEach(li -> {\n System.out.println(li);\n });*/\n }\n }\n \n \n package collection;\n \n import java.util.HashSet;\n import java.util.Set;\n \n /**\n * Created by zj on 2017/9/10.\n */\n public class Student {\n private String id;\n private String name;\n private Set courses;\n \n public Student() {\n }\n \n public Student(String id, String name) {\n this.id = id;\n this.name = name;\n this.courses = new HashSet();\n }\n \n public Student(String id, String name, Set courses) {\n this.id = id;\n this.name = name;\n this.courses = courses;\n }\n \n @Override\n public String toString() {\n return \"Student{\" +\n \"id='\" + id + '\\'' +\n \", name='\" + name + '\\'' +\n \", courses=\" + courses +\n '}';\n }\n \n @Override\n protected Object clone() throws CloneNotSupportedException {\n return new Student(this.id, this.name);\n }\n \n public String getId() {\n return id;\n }\n \n public void setId(String id) {\n this.id = id;\n }\n \n public String getName() {\n return name;\n }\n \n public void setName(String name) {\n this.name = name;\n }\n \n public Set getCourses() {\n return courses;\n }\n \n public void setCourses(Set courses) {\n this.courses = courses;\n }\n }\n\n\n public static void main(String[] args) {\n Map map = new HashMap();\n \n map.put(\"a\", new Student(\"A\", \"FFF\"));\n map.put(\"b\", new Student(\"B\", \"AAA\"));\n map.put(\"d\", new Student(\"D\", \"KKK\"));\n map.put(\"c\", new Student(\"C\", \"KKK\"));\n \n List> list =\n new ArrayList<>(map.entrySet());\n \n Collections.sort(list, new Comparator>() {\n @Override\n public int compare(Map.Entry o1, Map.Entry o2) {\n return o1.getValue().compareTo(o2.getValue());\n }\n });\n \n for (Map.Entry mapping : list) {\n System.out.println(mapping.getKey() + \":\" + mapping.getValue());\n }\n }\n\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170911_list_clone","published":1,"date":"2020-01-15T05:50:39.633Z","updated":"2021-03-10T13:50:15.395Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvb001724uj0amw9qf8","content":"

http://www.cnblogs.com/lyajs/p/5779021.html

\n \n
package collection;\n\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.ArrayList;\n\n/**\n * Created by zj on 2017/9/11.\n */\npublic class Link {\n\n    @Test\n    public void test() {\n        ArrayList<Student> list = new ArrayList<>();\n        //添加两个元素\n        Student stJack = new Student("Jack", "AAAA");\n        Student stTom = new Student("Tom", "BBBBB");\n        list.add(stJack);\n        list.add(stTom);\n        //克隆\n        ArrayList<Student> listCopy = new ArrayList<>();\n        /*list.forEach(li -> {\n            listCopy.add(li);\n        });*/\n        listCopy = (ArrayList<Student>) list.clone();\n\n        listCopy.get(1).setId("FFFFFFFFFFF");\n        System.out.println(list);\n        System.out.println(list.get(1).hashCode());\n        System.out.println(listCopy);\n        System.out.println(listCopy.get(1).hashCode());\n\n        /*ArrayList<String> ArrayList = new ArrayList<>();\n        ArrayList.add("A");\n        ArrayList.add("B");\n        ArrayList.add("C");\n        ArrayList.add("D");\n        ArrayList.add("E");\n\n        System.out.println(ArrayList.clone());\n\n        ArrayList.forEach(li -> {\n            System.out.println(li);\n        });*/\n    }\n}\n\n\npackage collection;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Created by zj on 2017/9/10.\n */\npublic class Student {\n    private String id;\n    private String name;\n    private Set courses;\n\n    public Student() {\n    }\n\n    public Student(String id, String name) {\n        this.id = id;\n        this.name = name;\n        this.courses = new HashSet();\n    }\n\n    public Student(String id, String name, Set courses) {\n        this.id = id;\n        this.name = name;\n        this.courses = courses;\n    }\n\n    @Override\n    public String toString() {\n        return "Student{" +\n                "id='" + id + '\\'' +\n                ", name='" + name + '\\'' +\n                ", courses=" + courses +\n                '}';\n    }\n\n    @Override\n    protected Object clone() throws CloneNotSupportedException {\n        return new Student(this.id, this.name);\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public Set getCourses() {\n        return courses;\n    }\n\n    public void setCourses(Set courses) {\n        this.courses = courses;\n    }\n}\n\n\npublic static void main(String[] args) {\n        Map<String, Student> map = new HashMap<String, Student>();\n\n        map.put("a", new Student("A", "FFF"));\n        map.put("b", new Student("B", "AAA"));\n        map.put("d", new Student("D", "KKK"));\n        map.put("c", new Student("C", "KKK"));\n\n        List<Map.Entry<String, Student>> list =\n                new ArrayList<>(map.entrySet());\n\n        Collections.sort(list, new Comparator<Map.Entry<String, Student>>() {\n            @Override\n            public int compare(Map.Entry<String, Student> o1, Map.Entry<String, Student> o2) {\n                return o1.getValue().compareTo(o2.getValue());\n            }\n        });\n\n        for (Map.Entry<String, Student> mapping : list) {\n            System.out.println(mapping.getKey() + ":" + mapping.getValue());\n        }\n    }\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"

http://www.cnblogs.com/lyajs/p/5779021.html

","more":"
package collection;\n\nimport org.junit.Test;\n\nimport java.util.ArrayList;\nimport java.util.ArrayList;\n\n/**\n * Created by zj on 2017/9/11.\n */\npublic class Link {\n\n    @Test\n    public void test() {\n        ArrayList<Student> list = new ArrayList<>();\n        //添加两个元素\n        Student stJack = new Student("Jack", "AAAA");\n        Student stTom = new Student("Tom", "BBBBB");\n        list.add(stJack);\n        list.add(stTom);\n        //克隆\n        ArrayList<Student> listCopy = new ArrayList<>();\n        /*list.forEach(li -> {\n            listCopy.add(li);\n        });*/\n        listCopy = (ArrayList<Student>) list.clone();\n\n        listCopy.get(1).setId("FFFFFFFFFFF");\n        System.out.println(list);\n        System.out.println(list.get(1).hashCode());\n        System.out.println(listCopy);\n        System.out.println(listCopy.get(1).hashCode());\n\n        /*ArrayList<String> ArrayList = new ArrayList<>();\n        ArrayList.add("A");\n        ArrayList.add("B");\n        ArrayList.add("C");\n        ArrayList.add("D");\n        ArrayList.add("E");\n\n        System.out.println(ArrayList.clone());\n\n        ArrayList.forEach(li -> {\n            System.out.println(li);\n        });*/\n    }\n}\n\n\npackage collection;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Created by zj on 2017/9/10.\n */\npublic class Student {\n    private String id;\n    private String name;\n    private Set courses;\n\n    public Student() {\n    }\n\n    public Student(String id, String name) {\n        this.id = id;\n        this.name = name;\n        this.courses = new HashSet();\n    }\n\n    public Student(String id, String name, Set courses) {\n        this.id = id;\n        this.name = name;\n        this.courses = courses;\n    }\n\n    @Override\n    public String toString() {\n        return "Student{" +\n                "id='" + id + '\\'' +\n                ", name='" + name + '\\'' +\n                ", courses=" + courses +\n                '}';\n    }\n\n    @Override\n    protected Object clone() throws CloneNotSupportedException {\n        return new Student(this.id, this.name);\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public Set getCourses() {\n        return courses;\n    }\n\n    public void setCourses(Set courses) {\n        this.courses = courses;\n    }\n}\n\n\npublic static void main(String[] args) {\n        Map<String, Student> map = new HashMap<String, Student>();\n\n        map.put("a", new Student("A", "FFF"));\n        map.put("b", new Student("B", "AAA"));\n        map.put("d", new Student("D", "KKK"));\n        map.put("c", new Student("C", "KKK"));\n\n        List<Map.Entry<String, Student>> list =\n                new ArrayList<>(map.entrySet());\n\n        Collections.sort(list, new Comparator<Map.Entry<String, Student>>() {\n            @Override\n            public int compare(Map.Entry<String, Student> o1, Map.Entry<String, Student> o2) {\n                return o1.getValue().compareTo(o2.getValue());\n            }\n        });\n\n        for (Map.Entry<String, Student> mapping : list) {\n            System.out.println(mapping.getKey() + ":" + mapping.getValue());\n        }\n    }\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"待完成","comments":1,"description":null,"_content":"http://www.cnblogs.com/XHJT/p/3897440.html\n\nhttp://blog.csdn.net/suifeng3051/article/details/52611233\n \n \n\n\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170911_syc.md","raw":"---\ntitle: 待完成\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\nhttp://www.cnblogs.com/XHJT/p/3897440.html\n\nhttp://blog.csdn.net/suifeng3051/article/details/52611233\n \n \n\n\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170911_syc","published":1,"date":"2020-01-15T05:50:39.635Z","updated":"2021-03-10T13:50:15.344Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvd001b24ujn786cghk","content":"

http://www.cnblogs.com/XHJT/p/3897440.html

\n

http://blog.csdn.net/suifeng3051/article/details/52611233

\n \n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"

http://www.cnblogs.com/XHJT/p/3897440.html

\n

http://blog.csdn.net/suifeng3051/article/details/52611233

","more":"
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"网络协议记录","comments":1,"description":null,"_content":" \n\n  {% qnimg 11185081040.jpg title:ISO七层模型与对应协议 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n  {% qnimg 11187081041.jpg title:ISO七层模型与对应协议 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n \n\n> ISO: (International Organization for Standards) 国际标准化组织\n\n> OSI(参考模型): (Open Systems Interconnection) 开放式通信系统互联网参考模型:把通信功能划分为7个分层\n\n> OSI协议: OSI协议以OSI参考模型为基础界定了每个阶层的协议和每个阶层之间接口相关的标准;遵循OSI协议的的产品为OSI产品;\n\n> MAC地址: (Media Access Control)介质访问控制;也叫物理地址、硬件地址;\n\n> Unicast: 单播;Broadcast: 广播;Multicast: 多播;Anycast: 任播\n\n> 网卡: NIC(Network Interface Card),使计算机联网的设备\n\n> 中继器: (Repeater) ,物理层上延长网络的设备(处于第一层)\n \n 由电缆传过来的电信号或光信号经由中继器的波形调整和放大再传给另一个电缆\n\n> 网桥: (Bridge) 也叫2层交换机,从数据链路层上延长网络的设备(处于第二层),根据MAC地址进行处理\n\n 能识别数据帧,并把数据帧临时存储于内存,并重新生成信号作为新的帧转发给相连的另一个网段\n \n 地址自学机制 地址过滤功能\n\n> 路由器: (Router) 也叫3层交换机,通过网络层转发分组数据的设备(处于第三层),根据IP地址处理\n\n> 4~7层交换机:处理传输层以上各层网络传输的设备\n\n> 网关: (Gateway) 转换协议的设备,协议转换,数据转发(从传输层到应用层的数据转换、转发)\n\n> FCS: (Frame Check Sequence) 帧检验序列,(网桥)校验数据是否正确的送达目的地\n\n> CRC: (Cyclic Redundancy Check) 循环冗余校验码,CRC循环冗余码校验FCS帧检验序列\n\n> ATM: (Asynchronous Transfer Mode) 异步传输\n\n> 网络协议族: (Internet Protocol Suite) 组成网际协议的一组协议\n\n> IETF: (The Internet Engineering Task Force) 国际互联网工程任务组,一个公开性质的大型民间国际团体\n\n> RFC: (Request For Comment) 记录TCP/IP协议及其实现和运用信息\n\n> 互联网: (The Internet) \n\n> NOC: (Network Operation Center) 网络操作中心,互联网中每个网络由NOC相连 \n\n> IX: (Internet Exchange) 网络交换中心,连接异构网络(不同运营商导致)需要IX支持\n\n> ISP: (Internet Service Provider) 互联网服务提供商\n\n> IP: (Internet Protocol)跨越网络传送数据包(分组交换)\n\n> ICMP: (Internet Control Message Protocol) Internet控制报文协议,\n\n 当IP数据包发送途中发生异常无法达到目的地址,需要给发送端发送一个异常的通知(可以诊断网络健康状况)\n\n> ARP: (Address Resolution Protocol)从分组数据包的IP地址中解析出物理地址(MAC地址)的一种协议\n\n> TCP: (Transmission Control Protocol) 传输控制协议,面向有连接,传输层协议,\n\n> UDP: (User Datagram Protocol) 用户数据报协议,面向无连接,传输层协议,\n\n> WWW: (World Wide Web) \n\n> HTTP: (HyperText Transfer Protocol) 超文本传输协议 \n\n> SMTP: (Simple Mail Transfer Protocol) \n\n> MIME: () (表示层协议)\n\n> FTP: (File Transfer Protocol) 文件传输协议\n\n> TELNET: 网络远程登陆协议 \n\n> SSH: (Secure Shell) \n\n> SNMP: (Simple Network Management Protocol) 简单网络管理协议 (应用层协议)\n\n 在TCP/IP中进行网络管理, 采用SNMP管理的主机、网桥、路由器称为SNMP代理(Agent),进行管理的那一段叫做管理器(Manager)\n Agent和Manager用到该协议\n 在SNMP代理端,保存网络接口的信息、通信数据量、异常数据量等信息\n 可以及时检查网络拥堵情况\n\n> MIB: () (表示层协议)\n\n> CSMA: (Carrier Sense Multiple Access) 载波侦听多路访问,争夺获取数据传输的权力\n\n> CSMA/CD: 相对于CSMA,要求每个站提前检测冲突,发生冲突,则尽早释放信道\n\n> FDDI: (Fiber Distributed Data Interface) 光纤分布式数据接口\n\n> BPDU: (Bridge Protocol Data Unit) 生成树方式中,每个网桥必须在每1-10秒内小胡交换BPDU包,用来判断哪些端口使用哪些不使用,以便消除环路\n\n> RSTP: (Rapid Spanning Tree Protocol) 能将发生环路时的恢复时间缩短到几秒之内 \n\n> 无线PAN: (Personal Area Network) 通信距离10米左右,应用:蓝牙\n\n> 无线LAN: (Local Area Network) 通信距离100米左右,应用:Wi-Fi\n\n> 无线MAN: (Metropolitan Area Network) 通信距离1km-100km,应用:WiMAX\n\n> 无线RAN: (Regional Area Network) 通信距离200k-700km\n\n> 无线WAN: (Wide Area Network) 应用3G,LTE,4G等\n\n> PPP: (Point to Point Protocol) 点对点协议,相当于2层的数据链路层\n\n ppp协议的主要功能中主要包括两个协议:\n \n 1.LCP:(Link Control Protocol),不依赖上层,\n \n 通过两次握手进行用户名密码验证,明文传输密码,不安全\n \n 主要负责建立和断开连接,设置最大接受单元(MRU,Maximum Receive Unit),\n 设置验证协议[PAP(Password Authentication Protocol) 或 CHAP(Challenge Handshake Authentication Protocol)],\n 设置进行通信质量监控与否\n \n 2.NCP: (Network Control Protocol), \n \n 依赖上层,如果上层是IP,则此时NCP也叫IPCP\n \n 使用一次性密码OTP(One Time Password), 安全,防止窃听\n \n IPCP主要IP地址设置以及是否进行TCP/IP首部压缩等设置\n \n \n ppp协议连接时,需要进行用户名密码验证\n \n> PPPoE: (PPP over Ethernet) 互联网接入服务商在以太网上提供PPP功能,可以提供计费功能\n\n> ADSL: (Asymmetric Digital Subscriber Line) 非对称数字用户环路\n\n> FTTH: (Fiber To The Home) 光纤到户\n\n> VPN: (Virtual Private Network) 虚拟专用网络\n\n> MTU: (Maximum Transmission Unit) 最大传输单元\n\n> CIDR: (Classless Inter-Domain Routing) 无类型域间选路\n\n> CIDR: (Border Gateway Protocol) 边界网关协议\n\n> VLSM: (Variable Length Subnet Mask) 可变长子网掩码\n\n> ICANN: (Internet Corporation for Assigned Names and Numbers) 互联网名称与数字地址分配机构\n\n> NAT: (Network Address Translation) 网络地址转换\n\n\n----\n----\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170918_protocol.md","raw":"---\ntitle: 网络协议记录\n\ncomments: true \n\ntags: \n - 定义\n\ncategories: \n - 计算机网络\n\ndescription: \n\n---\n \n\n  {% qnimg 11185081040.jpg title:ISO七层模型与对应协议 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n  {% qnimg 11187081041.jpg title:ISO七层模型与对应协议 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n \n\n> ISO: (International Organization for Standards) 国际标准化组织\n\n> OSI(参考模型): (Open Systems Interconnection) 开放式通信系统互联网参考模型:把通信功能划分为7个分层\n\n> OSI协议: OSI协议以OSI参考模型为基础界定了每个阶层的协议和每个阶层之间接口相关的标准;遵循OSI协议的的产品为OSI产品;\n\n> MAC地址: (Media Access Control)介质访问控制;也叫物理地址、硬件地址;\n\n> Unicast: 单播;Broadcast: 广播;Multicast: 多播;Anycast: 任播\n\n> 网卡: NIC(Network Interface Card),使计算机联网的设备\n\n> 中继器: (Repeater) ,物理层上延长网络的设备(处于第一层)\n \n 由电缆传过来的电信号或光信号经由中继器的波形调整和放大再传给另一个电缆\n\n> 网桥: (Bridge) 也叫2层交换机,从数据链路层上延长网络的设备(处于第二层),根据MAC地址进行处理\n\n 能识别数据帧,并把数据帧临时存储于内存,并重新生成信号作为新的帧转发给相连的另一个网段\n \n 地址自学机制 地址过滤功能\n\n> 路由器: (Router) 也叫3层交换机,通过网络层转发分组数据的设备(处于第三层),根据IP地址处理\n\n> 4~7层交换机:处理传输层以上各层网络传输的设备\n\n> 网关: (Gateway) 转换协议的设备,协议转换,数据转发(从传输层到应用层的数据转换、转发)\n\n> FCS: (Frame Check Sequence) 帧检验序列,(网桥)校验数据是否正确的送达目的地\n\n> CRC: (Cyclic Redundancy Check) 循环冗余校验码,CRC循环冗余码校验FCS帧检验序列\n\n> ATM: (Asynchronous Transfer Mode) 异步传输\n\n> 网络协议族: (Internet Protocol Suite) 组成网际协议的一组协议\n\n> IETF: (The Internet Engineering Task Force) 国际互联网工程任务组,一个公开性质的大型民间国际团体\n\n> RFC: (Request For Comment) 记录TCP/IP协议及其实现和运用信息\n\n> 互联网: (The Internet) \n\n> NOC: (Network Operation Center) 网络操作中心,互联网中每个网络由NOC相连 \n\n> IX: (Internet Exchange) 网络交换中心,连接异构网络(不同运营商导致)需要IX支持\n\n> ISP: (Internet Service Provider) 互联网服务提供商\n\n> IP: (Internet Protocol)跨越网络传送数据包(分组交换)\n\n> ICMP: (Internet Control Message Protocol) Internet控制报文协议,\n\n 当IP数据包发送途中发生异常无法达到目的地址,需要给发送端发送一个异常的通知(可以诊断网络健康状况)\n\n> ARP: (Address Resolution Protocol)从分组数据包的IP地址中解析出物理地址(MAC地址)的一种协议\n\n> TCP: (Transmission Control Protocol) 传输控制协议,面向有连接,传输层协议,\n\n> UDP: (User Datagram Protocol) 用户数据报协议,面向无连接,传输层协议,\n\n> WWW: (World Wide Web) \n\n> HTTP: (HyperText Transfer Protocol) 超文本传输协议 \n\n> SMTP: (Simple Mail Transfer Protocol) \n\n> MIME: () (表示层协议)\n\n> FTP: (File Transfer Protocol) 文件传输协议\n\n> TELNET: 网络远程登陆协议 \n\n> SSH: (Secure Shell) \n\n> SNMP: (Simple Network Management Protocol) 简单网络管理协议 (应用层协议)\n\n 在TCP/IP中进行网络管理, 采用SNMP管理的主机、网桥、路由器称为SNMP代理(Agent),进行管理的那一段叫做管理器(Manager)\n Agent和Manager用到该协议\n 在SNMP代理端,保存网络接口的信息、通信数据量、异常数据量等信息\n 可以及时检查网络拥堵情况\n\n> MIB: () (表示层协议)\n\n> CSMA: (Carrier Sense Multiple Access) 载波侦听多路访问,争夺获取数据传输的权力\n\n> CSMA/CD: 相对于CSMA,要求每个站提前检测冲突,发生冲突,则尽早释放信道\n\n> FDDI: (Fiber Distributed Data Interface) 光纤分布式数据接口\n\n> BPDU: (Bridge Protocol Data Unit) 生成树方式中,每个网桥必须在每1-10秒内小胡交换BPDU包,用来判断哪些端口使用哪些不使用,以便消除环路\n\n> RSTP: (Rapid Spanning Tree Protocol) 能将发生环路时的恢复时间缩短到几秒之内 \n\n> 无线PAN: (Personal Area Network) 通信距离10米左右,应用:蓝牙\n\n> 无线LAN: (Local Area Network) 通信距离100米左右,应用:Wi-Fi\n\n> 无线MAN: (Metropolitan Area Network) 通信距离1km-100km,应用:WiMAX\n\n> 无线RAN: (Regional Area Network) 通信距离200k-700km\n\n> 无线WAN: (Wide Area Network) 应用3G,LTE,4G等\n\n> PPP: (Point to Point Protocol) 点对点协议,相当于2层的数据链路层\n\n ppp协议的主要功能中主要包括两个协议:\n \n 1.LCP:(Link Control Protocol),不依赖上层,\n \n 通过两次握手进行用户名密码验证,明文传输密码,不安全\n \n 主要负责建立和断开连接,设置最大接受单元(MRU,Maximum Receive Unit),\n 设置验证协议[PAP(Password Authentication Protocol) 或 CHAP(Challenge Handshake Authentication Protocol)],\n 设置进行通信质量监控与否\n \n 2.NCP: (Network Control Protocol), \n \n 依赖上层,如果上层是IP,则此时NCP也叫IPCP\n \n 使用一次性密码OTP(One Time Password), 安全,防止窃听\n \n IPCP主要IP地址设置以及是否进行TCP/IP首部压缩等设置\n \n \n ppp协议连接时,需要进行用户名密码验证\n \n> PPPoE: (PPP over Ethernet) 互联网接入服务商在以太网上提供PPP功能,可以提供计费功能\n\n> ADSL: (Asymmetric Digital Subscriber Line) 非对称数字用户环路\n\n> FTTH: (Fiber To The Home) 光纤到户\n\n> VPN: (Virtual Private Network) 虚拟专用网络\n\n> MTU: (Maximum Transmission Unit) 最大传输单元\n\n> CIDR: (Classless Inter-Domain Routing) 无类型域间选路\n\n> CIDR: (Border Gateway Protocol) 边界网关协议\n\n> VLSM: (Variable Length Subnet Mask) 可变长子网掩码\n\n> ICANN: (Internet Corporation for Assigned Names and Numbers) 互联网名称与数字地址分配机构\n\n> NAT: (Network Address Translation) 网络地址转换\n\n\n----\n----\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170918_protocol","published":1,"date":"2020-01-15T05:50:39.636Z","updated":"2021-03-10T13:50:15.321Z","layout":"post","photos":[],"link":"","_id":"ckm3inuve001c24uj4xw9xe8r","content":"

  \"图片说明\"

\n

  \"图片说明\"

\n \n
\n

ISO: (International Organization for Standards) 国际标准化组织

\n
\n
\n

OSI(参考模型): (Open Systems Interconnection) 开放式通信系统互联网参考模型:把通信功能划分为7个分层

\n
\n
\n

OSI协议: OSI协议以OSI参考模型为基础界定了每个阶层的协议和每个阶层之间接口相关的标准;遵循OSI协议的的产品为OSI产品;

\n
\n
\n

MAC地址: (Media Access Control)介质访问控制;也叫物理地址、硬件地址;

\n
\n
\n

Unicast: 单播;Broadcast: 广播;Multicast: 多播;Anycast: 任播

\n
\n
\n

网卡: NIC(Network Interface Card),使计算机联网的设备

\n
\n
\n

中继器: (Repeater) ,物理层上延长网络的设备(处于第一层)

\n
\n
由电缆传过来的电信号或光信号经由中继器的波形调整和放大再传给另一个电缆\n
\n

网桥: (Bridge) 也叫2层交换机,从数据链路层上延长网络的设备(处于第二层),根据MAC地址进行处理

\n
\n
能识别数据帧,并把数据帧临时存储于内存,并重新生成信号作为新的帧转发给相连的另一个网段\n\n地址自学机制 地址过滤功能\n
\n

路由器: (Router) 也叫3层交换机,通过网络层转发分组数据的设备(处于第三层),根据IP地址处理

\n
\n
\n

4~7层交换机:处理传输层以上各层网络传输的设备

\n
\n
\n

网关: (Gateway) 转换协议的设备,协议转换,数据转发(从传输层到应用层的数据转换、转发)

\n
\n
\n

FCS: (Frame Check Sequence) 帧检验序列,(网桥)校验数据是否正确的送达目的地

\n
\n
\n

CRC: (Cyclic Redundancy Check) 循环冗余校验码,CRC循环冗余码校验FCS帧检验序列

\n
\n
\n

ATM: (Asynchronous Transfer Mode) 异步传输

\n
\n
\n

网络协议族: (Internet Protocol Suite) 组成网际协议的一组协议

\n
\n
\n

IETF: (The Internet Engineering Task Force) 国际互联网工程任务组,一个公开性质的大型民间国际团体

\n
\n
\n

RFC: (Request For Comment) 记录TCP/IP协议及其实现和运用信息

\n
\n
\n

互联网: (The Internet)

\n
\n
\n

NOC: (Network Operation Center) 网络操作中心,互联网中每个网络由NOC相连

\n
\n
\n

IX: (Internet Exchange) 网络交换中心,连接异构网络(不同运营商导致)需要IX支持

\n
\n
\n

ISP: (Internet Service Provider) 互联网服务提供商

\n
\n
\n

IP: (Internet Protocol)跨越网络传送数据包(分组交换)

\n
\n
\n

ICMP: (Internet Control Message Protocol) Internet控制报文协议,

\n
\n
当IP数据包发送途中发生异常无法达到目的地址,需要给发送端发送一个异常的通知(可以诊断网络健康状况)\n
\n

ARP: (Address Resolution Protocol)从分组数据包的IP地址中解析出物理地址(MAC地址)的一种协议

\n
\n
\n

TCP: (Transmission Control Protocol) 传输控制协议,面向有连接,传输层协议,

\n
\n
\n

UDP: (User Datagram Protocol) 用户数据报协议,面向无连接,传输层协议,

\n
\n
\n

WWW: (World Wide Web)

\n
\n
\n

HTTP: (HyperText Transfer Protocol) 超文本传输协议

\n
\n
\n

SMTP: (Simple Mail Transfer Protocol)

\n
\n
\n

MIME: () (表示层协议)

\n
\n
\n

FTP: (File Transfer Protocol) 文件传输协议

\n
\n
\n

TELNET: 网络远程登陆协议

\n
\n
\n

SSH: (Secure Shell)

\n
\n
\n

SNMP: (Simple Network Management Protocol) 简单网络管理协议 (应用层协议)

\n
\n
在TCP/IP中进行网络管理, 采用SNMP管理的主机、网桥、路由器称为SNMP代理(Agent),进行管理的那一段叫做管理器(Manager)\nAgent和Manager用到该协议\n在SNMP代理端,保存网络接口的信息、通信数据量、异常数据量等信息\n可以及时检查网络拥堵情况\n
\n

MIB: () (表示层协议)

\n
\n
\n

CSMA: (Carrier Sense Multiple Access) 载波侦听多路访问,争夺获取数据传输的权力

\n
\n
\n

CSMA/CD: 相对于CSMA,要求每个站提前检测冲突,发生冲突,则尽早释放信道

\n
\n
\n

FDDI: (Fiber Distributed Data Interface) 光纤分布式数据接口

\n
\n
\n

BPDU: (Bridge Protocol Data Unit) 生成树方式中,每个网桥必须在每1-10秒内小胡交换BPDU包,用来判断哪些端口使用哪些不使用,以便消除环路

\n
\n
\n

RSTP: (Rapid Spanning Tree Protocol) 能将发生环路时的恢复时间缩短到几秒之内

\n
\n
\n

无线PAN: (Personal Area Network) 通信距离10米左右,应用:蓝牙

\n
\n
\n

无线LAN: (Local Area Network) 通信距离100米左右,应用:Wi-Fi

\n
\n
\n

无线MAN: (Metropolitan Area Network) 通信距离1km-100km,应用:WiMAX

\n
\n
\n

无线RAN: (Regional Area Network) 通信距离200k-700km

\n
\n
\n

无线WAN: (Wide Area Network) 应用3G,LTE,4G等

\n
\n
\n

PPP: (Point to Point Protocol) 点对点协议,相当于2层的数据链路层

\n
\n
ppp协议的主要功能中主要包括两个协议:\n\n    1.LCP:(Link Control Protocol),不依赖上层,\n\n        通过两次握手进行用户名密码验证,明文传输密码,不安全\n\n        主要负责建立和断开连接,设置最大接受单元(MRU,Maximum Receive Unit),\n        设置验证协议[PAP(Password Authentication Protocol) 或 CHAP(Challenge Handshake Authentication Protocol)],\n        设置进行通信质量监控与否\n\n    2.NCP: (Network Control Protocol), \n\n        依赖上层,如果上层是IP,则此时NCP也叫IPCP\n\n        使用一次性密码OTP(One Time Password), 安全,防止窃听\n\n        IPCP主要IP地址设置以及是否进行TCP/IP首部压缩等设置\n\n\n    ppp协议连接时,需要进行用户名密码验证\n
\n

PPPoE: (PPP over Ethernet) 互联网接入服务商在以太网上提供PPP功能,可以提供计费功能

\n
\n
\n

ADSL: (Asymmetric Digital Subscriber Line) 非对称数字用户环路

\n
\n
\n

FTTH: (Fiber To The Home) 光纤到户

\n
\n
\n

VPN: (Virtual Private Network) 虚拟专用网络

\n
\n
\n

MTU: (Maximum Transmission Unit) 最大传输单元

\n
\n
\n

CIDR: (Classless Inter-Domain Routing) 无类型域间选路

\n
\n
\n

CIDR: (Border Gateway Protocol) 边界网关协议

\n
\n
\n

VLSM: (Variable Length Subnet Mask) 可变长子网掩码

\n
\n
\n

ICANN: (Internet Corporation for Assigned Names and Numbers) 互联网名称与数字地址分配机构

\n
\n
\n

NAT: (Network Address Translation) 网络地址转换

\n
\n
\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"

  \"图片说明\"

\n

  \"图片说明\"

","more":"
\n

ISO: (International Organization for Standards) 国际标准化组织

\n
\n
\n

OSI(参考模型): (Open Systems Interconnection) 开放式通信系统互联网参考模型:把通信功能划分为7个分层

\n
\n
\n

OSI协议: OSI协议以OSI参考模型为基础界定了每个阶层的协议和每个阶层之间接口相关的标准;遵循OSI协议的的产品为OSI产品;

\n
\n
\n

MAC地址: (Media Access Control)介质访问控制;也叫物理地址、硬件地址;

\n
\n
\n

Unicast: 单播;Broadcast: 广播;Multicast: 多播;Anycast: 任播

\n
\n
\n

网卡: NIC(Network Interface Card),使计算机联网的设备

\n
\n
\n

中继器: (Repeater) ,物理层上延长网络的设备(处于第一层)

\n
\n
由电缆传过来的电信号或光信号经由中继器的波形调整和放大再传给另一个电缆\n
\n

网桥: (Bridge) 也叫2层交换机,从数据链路层上延长网络的设备(处于第二层),根据MAC地址进行处理

\n
\n
能识别数据帧,并把数据帧临时存储于内存,并重新生成信号作为新的帧转发给相连的另一个网段\n\n地址自学机制 地址过滤功能\n
\n

路由器: (Router) 也叫3层交换机,通过网络层转发分组数据的设备(处于第三层),根据IP地址处理

\n
\n
\n

4~7层交换机:处理传输层以上各层网络传输的设备

\n
\n
\n

网关: (Gateway) 转换协议的设备,协议转换,数据转发(从传输层到应用层的数据转换、转发)

\n
\n
\n

FCS: (Frame Check Sequence) 帧检验序列,(网桥)校验数据是否正确的送达目的地

\n
\n
\n

CRC: (Cyclic Redundancy Check) 循环冗余校验码,CRC循环冗余码校验FCS帧检验序列

\n
\n
\n

ATM: (Asynchronous Transfer Mode) 异步传输

\n
\n
\n

网络协议族: (Internet Protocol Suite) 组成网际协议的一组协议

\n
\n
\n

IETF: (The Internet Engineering Task Force) 国际互联网工程任务组,一个公开性质的大型民间国际团体

\n
\n
\n

RFC: (Request For Comment) 记录TCP/IP协议及其实现和运用信息

\n
\n
\n

互联网: (The Internet)

\n
\n
\n

NOC: (Network Operation Center) 网络操作中心,互联网中每个网络由NOC相连

\n
\n
\n

IX: (Internet Exchange) 网络交换中心,连接异构网络(不同运营商导致)需要IX支持

\n
\n
\n

ISP: (Internet Service Provider) 互联网服务提供商

\n
\n
\n

IP: (Internet Protocol)跨越网络传送数据包(分组交换)

\n
\n
\n

ICMP: (Internet Control Message Protocol) Internet控制报文协议,

\n
\n
当IP数据包发送途中发生异常无法达到目的地址,需要给发送端发送一个异常的通知(可以诊断网络健康状况)\n
\n

ARP: (Address Resolution Protocol)从分组数据包的IP地址中解析出物理地址(MAC地址)的一种协议

\n
\n
\n

TCP: (Transmission Control Protocol) 传输控制协议,面向有连接,传输层协议,

\n
\n
\n

UDP: (User Datagram Protocol) 用户数据报协议,面向无连接,传输层协议,

\n
\n
\n

WWW: (World Wide Web)

\n
\n
\n

HTTP: (HyperText Transfer Protocol) 超文本传输协议

\n
\n
\n

SMTP: (Simple Mail Transfer Protocol)

\n
\n
\n

MIME: () (表示层协议)

\n
\n
\n

FTP: (File Transfer Protocol) 文件传输协议

\n
\n
\n

TELNET: 网络远程登陆协议

\n
\n
\n

SSH: (Secure Shell)

\n
\n
\n

SNMP: (Simple Network Management Protocol) 简单网络管理协议 (应用层协议)

\n
\n
在TCP/IP中进行网络管理, 采用SNMP管理的主机、网桥、路由器称为SNMP代理(Agent),进行管理的那一段叫做管理器(Manager)\nAgent和Manager用到该协议\n在SNMP代理端,保存网络接口的信息、通信数据量、异常数据量等信息\n可以及时检查网络拥堵情况\n
\n

MIB: () (表示层协议)

\n
\n
\n

CSMA: (Carrier Sense Multiple Access) 载波侦听多路访问,争夺获取数据传输的权力

\n
\n
\n

CSMA/CD: 相对于CSMA,要求每个站提前检测冲突,发生冲突,则尽早释放信道

\n
\n
\n

FDDI: (Fiber Distributed Data Interface) 光纤分布式数据接口

\n
\n
\n

BPDU: (Bridge Protocol Data Unit) 生成树方式中,每个网桥必须在每1-10秒内小胡交换BPDU包,用来判断哪些端口使用哪些不使用,以便消除环路

\n
\n
\n

RSTP: (Rapid Spanning Tree Protocol) 能将发生环路时的恢复时间缩短到几秒之内

\n
\n
\n

无线PAN: (Personal Area Network) 通信距离10米左右,应用:蓝牙

\n
\n
\n

无线LAN: (Local Area Network) 通信距离100米左右,应用:Wi-Fi

\n
\n
\n

无线MAN: (Metropolitan Area Network) 通信距离1km-100km,应用:WiMAX

\n
\n
\n

无线RAN: (Regional Area Network) 通信距离200k-700km

\n
\n
\n

无线WAN: (Wide Area Network) 应用3G,LTE,4G等

\n
\n
\n

PPP: (Point to Point Protocol) 点对点协议,相当于2层的数据链路层

\n
\n
ppp协议的主要功能中主要包括两个协议:\n\n    1.LCP:(Link Control Protocol),不依赖上层,\n\n        通过两次握手进行用户名密码验证,明文传输密码,不安全\n\n        主要负责建立和断开连接,设置最大接受单元(MRU,Maximum Receive Unit),\n        设置验证协议[PAP(Password Authentication Protocol) 或 CHAP(Challenge Handshake Authentication Protocol)],\n        设置进行通信质量监控与否\n\n    2.NCP: (Network Control Protocol), \n\n        依赖上层,如果上层是IP,则此时NCP也叫IPCP\n\n        使用一次性密码OTP(One Time Password), 安全,防止窃听\n\n        IPCP主要IP地址设置以及是否进行TCP/IP首部压缩等设置\n\n\n    ppp协议连接时,需要进行用户名密码验证\n
\n

PPPoE: (PPP over Ethernet) 互联网接入服务商在以太网上提供PPP功能,可以提供计费功能

\n
\n
\n

ADSL: (Asymmetric Digital Subscriber Line) 非对称数字用户环路

\n
\n
\n

FTTH: (Fiber To The Home) 光纤到户

\n
\n
\n

VPN: (Virtual Private Network) 虚拟专用网络

\n
\n
\n

MTU: (Maximum Transmission Unit) 最大传输单元

\n
\n
\n

CIDR: (Classless Inter-Domain Routing) 无类型域间选路

\n
\n
\n

CIDR: (Border Gateway Protocol) 边界网关协议

\n
\n
\n

VLSM: (Variable Length Subnet Mask) 可变长子网掩码

\n
\n
\n

ICANN: (Internet Corporation for Assigned Names and Numbers) 互联网名称与数字地址分配机构

\n
\n
\n

NAT: (Network Address Translation) 网络地址转换

\n
\n
\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"JVM的年轻代以及GC回收细节","comments":1,"description":null,"_content":"\n\n> 转载自并发编程网 链接地址: http://ifeve.com/jvm-yong-generation/\n\n\n\n## 为什么会有年轻代\n\n我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。\n\n\n## 年轻代中的GC\n\nHotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。\n\n因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。\n\n在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。\n\n\n{% qnimg young_gc.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n## 一个对象的这一辈子\n\n我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。\n\n## 有关年轻代的JVM参数\n\n1)-XX:NewSize和-XX:MaxNewSize\n\n用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。\n\n2)-XX:SurvivorRatio\n\n用于设置Eden和其中一个Survivor的比值,这个值也比较重要。\n\n3)-XX:+PrintTenuringDistribution\n\n这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。\n\n4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold\n\n用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170924_JVM.md","raw":"---\ntitle: JVM的年轻代以及GC回收细节\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n \n---\n\n\n> 转载自并发编程网 链接地址: http://ifeve.com/jvm-yong-generation/\n\n\n\n## 为什么会有年轻代\n\n我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。\n\n\n## 年轻代中的GC\n\nHotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。\n\n因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。\n\n在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。\n\n\n{% qnimg young_gc.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n## 一个对象的这一辈子\n\n我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。\n\n## 有关年轻代的JVM参数\n\n1)-XX:NewSize和-XX:MaxNewSize\n\n用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。\n\n2)-XX:SurvivorRatio\n\n用于设置Eden和其中一个Survivor的比值,这个值也比较重要。\n\n3)-XX:+PrintTenuringDistribution\n\n这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。\n\n4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold\n\n用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170924_JVM","published":1,"date":"2020-01-15T05:50:39.641Z","updated":"2021-03-10T13:50:15.327Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvh001g24ujoslwoh5z","content":"
\n

转载自并发编程网 链接地址: http://ifeve.com/jvm-yong-generation/

\n
\n\n

为什么会有年轻代

我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

\n

年轻代中的GC

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

\n

因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

\n

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

\n\"图片说明\"\n

一个对象的这一辈子

我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

\n

有关年轻代的JVM参数

1)-XX:NewSize和-XX:MaxNewSize

\n

用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。

\n

2)-XX:SurvivorRatio

\n

用于设置Eden和其中一个Survivor的比值,这个值也比较重要。

\n

3)-XX:+PrintTenuringDistribution

\n

这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。

\n

4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold

\n

用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

转载自并发编程网 链接地址: http://ifeve.com/jvm-yong-generation/

\n
","more":"

为什么会有年轻代

我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。

\n

年轻代中的GC

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

\n

因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。

\n

在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

\n\"图片说明\"\n

一个对象的这一辈子

我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

\n

有关年轻代的JVM参数

1)-XX:NewSize和-XX:MaxNewSize

\n

用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。

\n

2)-XX:SurvivorRatio

\n

用于设置Eden和其中一个Survivor的比值,这个值也比较重要。

\n

3)-XX:+PrintTenuringDistribution

\n

这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。

\n

4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold

\n

用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"Python 爬虫实战(4)","comments":1,"description":null,"toc":true,"_content":"\n> 多线程\n\n*待添加 条件变量 condition*\n\n\n\n## 个人代码:\n\n```python\n\n#生产者消费者\nimport threading # , _thread ()python2x是thread\nimport time\nimport queue #python2x是Queue\nimport random\n\nmutex = threading.Lock() #等价于_thread.allocate_lock(),也等价于_thread.allocate()\nmyq = queue.Queue(10)\nnum = range(10)\n\nclass Producer(threading.Thread):\n def run(self):\n global myq, num\n for i in range(30):\n time.sleep(0.3)\n if not myq.full() and mutex.acquire():\n info = random.choice(num)\n myq.put(info)\n print(\"put in data: \", info, \"; queue size\", myq.qsize())\n mutex.release()\n\nclass Consumer(threading.Thread):\n def run(self):\n global myq, num\n for i in range(30):\n time.sleep(1)\n if not myq.empty() and mutex.acquire():\n info = myq.get()\n print(\"get out data: \", info, \"; queue size\", myq.qsize())\n mutex.release()\n\ndef main():\n print ('main')\n p = Producer()\n c = Consumer()\n p.start()\n c.start()\n\nif __name__ == '__main__':\n main()\n```\n\n## 别人家的代码【滑稽】:\n\n```python\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n\nimport thread, threading\nimport urllib2\nimport time, random\nimport Queue\n\nshare_queue = Queue.Queue() #共享队列\nmy_lock = thread.allocate_lock()\nclass Producer(threading.Thread) :\n\n def run(self) :\n products = range(5)\n global share_queue\n while True :\n num = random.choice(products)\n my_lock.acquire()\n share_queue.put(num)\n print \"Produce : \", num\n my_lock.release()\n time.sleep(random.random())\n\nclass Consumer(threading.Thread) :\n\n def run(self) :\n global share_queue\n while True:\n my_lock.acquire()\n if share_queue.empty() : #这里没有使用信号量机制进行阻塞等待, \n print \"Queue is Empty...\" \n my_lock.release()\n time.sleep(random.random())\n continue\n num = share_queue.get()\n print \"Consumer : \", num\n my_lock.release()\n time.sleep(random.random())\n\ndef main() :\n producer = Producer()\n consumer = Consumer()\n producer.start()\n consumer.start()\n\nif __name__ == '__main__':\n main()\n\n```\n\n> 转载自 链接地址: http://www.jianshu.com/p/86b8e78c418a\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20171222_crawler4.md","raw":"---\ntitle: Python 爬虫实战(4)\n\ncomments: true\n\ntags:\n - python\n - 爬虫\n\ncategories:\n - 爬虫\n\ndescription:\n\ntoc: true\n \n---\n\n> 多线程\n\n*待添加 条件变量 condition*\n\n\n\n## 个人代码:\n\n```python\n\n#生产者消费者\nimport threading # , _thread ()python2x是thread\nimport time\nimport queue #python2x是Queue\nimport random\n\nmutex = threading.Lock() #等价于_thread.allocate_lock(),也等价于_thread.allocate()\nmyq = queue.Queue(10)\nnum = range(10)\n\nclass Producer(threading.Thread):\n def run(self):\n global myq, num\n for i in range(30):\n time.sleep(0.3)\n if not myq.full() and mutex.acquire():\n info = random.choice(num)\n myq.put(info)\n print(\"put in data: \", info, \"; queue size\", myq.qsize())\n mutex.release()\n\nclass Consumer(threading.Thread):\n def run(self):\n global myq, num\n for i in range(30):\n time.sleep(1)\n if not myq.empty() and mutex.acquire():\n info = myq.get()\n print(\"get out data: \", info, \"; queue size\", myq.qsize())\n mutex.release()\n\ndef main():\n print ('main')\n p = Producer()\n c = Consumer()\n p.start()\n c.start()\n\nif __name__ == '__main__':\n main()\n```\n\n## 别人家的代码【滑稽】:\n\n```python\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n\nimport thread, threading\nimport urllib2\nimport time, random\nimport Queue\n\nshare_queue = Queue.Queue() #共享队列\nmy_lock = thread.allocate_lock()\nclass Producer(threading.Thread) :\n\n def run(self) :\n products = range(5)\n global share_queue\n while True :\n num = random.choice(products)\n my_lock.acquire()\n share_queue.put(num)\n print \"Produce : \", num\n my_lock.release()\n time.sleep(random.random())\n\nclass Consumer(threading.Thread) :\n\n def run(self) :\n global share_queue\n while True:\n my_lock.acquire()\n if share_queue.empty() : #这里没有使用信号量机制进行阻塞等待, \n print \"Queue is Empty...\" \n my_lock.release()\n time.sleep(random.random())\n continue\n num = share_queue.get()\n print \"Consumer : \", num\n my_lock.release()\n time.sleep(random.random())\n\ndef main() :\n producer = Producer()\n consumer = Consumer()\n producer.start()\n consumer.start()\n\nif __name__ == '__main__':\n main()\n\n```\n\n> 转载自 链接地址: http://www.jianshu.com/p/86b8e78c418a\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20171222_crawler4","published":1,"date":"2020-01-15T05:50:39.650Z","updated":"2021-03-10T13:50:15.382Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvi001i24uj04zs22lx","content":"
\n

多线程

\n
\n

待添加 条件变量 condition

\n\n

个人代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

#生产者消费者
import threading # , _thread ()python2x是thread
import time
import queue #python2x是Queue
import random

mutex = threading.Lock() #等价于_thread.allocate_lock(),也等价于_thread.allocate()
myq = queue.Queue(10)
num = range(10)

class Producer(threading.Thread):
def run(self):
global myq, num
for i in range(30):
time.sleep(0.3)
if not myq.full() and mutex.acquire():
info = random.choice(num)
myq.put(info)
print(\"put in data: \", info, \"; queue size\", myq.qsize())
mutex.release()

class Consumer(threading.Thread):
def run(self):
global myq, num
for i in range(30):
time.sleep(1)
if not myq.empty() and mutex.acquire():
info = myq.get()
print(\"get out data: \", info, \"; queue size\", myq.qsize())
mutex.release()

def main():
print ('main')
p = Producer()
c = Consumer()
p.start()
c.start()

if __name__ == '__main__':
main()
\n

别人家的代码【滑稽】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import thread, threading
import urllib2
import time, random
import Queue

share_queue = Queue.Queue() #共享队列
my_lock = thread.allocate_lock()
class Producer(threading.Thread) :

def run(self) :
products = range(5)
global share_queue
while True :
num = random.choice(products)
my_lock.acquire()
share_queue.put(num)
print \"Produce : \", num
my_lock.release()
time.sleep(random.random())

class Consumer(threading.Thread) :

def run(self) :
global share_queue
while True:
my_lock.acquire()
if share_queue.empty() : #这里没有使用信号量机制进行阻塞等待,
print \"Queue is Empty...\"
my_lock.release()
time.sleep(random.random())
continue
num = share_queue.get()
print \"Consumer : \", num
my_lock.release()
time.sleep(random.random())

def main() :
producer = Producer()
consumer = Consumer()
producer.start()
consumer.start()

if __name__ == '__main__':
main()
\n
\n

转载自 链接地址: http://www.jianshu.com/p/86b8e78c418a

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

多线程

\n
\n

待添加 条件变量 condition

","more":"

个人代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

#生产者消费者
import threading # , _thread ()python2x是thread
import time
import queue #python2x是Queue
import random

mutex = threading.Lock() #等价于_thread.allocate_lock(),也等价于_thread.allocate()
myq = queue.Queue(10)
num = range(10)

class Producer(threading.Thread):
def run(self):
global myq, num
for i in range(30):
time.sleep(0.3)
if not myq.full() and mutex.acquire():
info = random.choice(num)
myq.put(info)
print(\"put in data: \", info, \"; queue size\", myq.qsize())
mutex.release()

class Consumer(threading.Thread):
def run(self):
global myq, num
for i in range(30):
time.sleep(1)
if not myq.empty() and mutex.acquire():
info = myq.get()
print(\"get out data: \", info, \"; queue size\", myq.qsize())
mutex.release()

def main():
print ('main')
p = Producer()
c = Consumer()
p.start()
c.start()

if __name__ == '__main__':
main()
\n

别人家的代码【滑稽】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import thread, threading
import urllib2
import time, random
import Queue

share_queue = Queue.Queue() #共享队列
my_lock = thread.allocate_lock()
class Producer(threading.Thread) :

def run(self) :
products = range(5)
global share_queue
while True :
num = random.choice(products)
my_lock.acquire()
share_queue.put(num)
print \"Produce : \", num
my_lock.release()
time.sleep(random.random())

class Consumer(threading.Thread) :

def run(self) :
global share_queue
while True:
my_lock.acquire()
if share_queue.empty() : #这里没有使用信号量机制进行阻塞等待,
print \"Queue is Empty...\"
my_lock.release()
time.sleep(random.random())
continue
num = share_queue.get()
print \"Consumer : \", num
my_lock.release()
time.sleep(random.random())

def main() :
producer = Producer()
consumer = Consumer()
producer.start()
consumer.start()

if __name__ == '__main__':
main()
\n
\n

转载自 链接地址: http://www.jianshu.com/p/86b8e78c418a

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"TypeError, a bytes-like object is required, not 'str'","comments":1,"description":"str和bytes类型之间的常用转码方式","toc":true,"_content":"\n> 问题分析: 该问题主要是由于当前操作的字符串是bytes类型的字符串对象,并对该bytes类型的字符串对象进行按照str类型的操作。\n\n{% qnimg 20171226111345.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n* 解决办法,将s转码成为str类型\n\n{% qnimg 20171226111804.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n\n\n## str和bytes类型之间的常用转码方式\n\n> str to bytes\n\n```python\ns = 'ab bbb'\nprint (type(s))\n\nb = bytes(s, encoding = 'utf-8')\nprint (type(b))\n\nb2 = s.encode('utf-8')\nprint (type(b2))\n\nb3 = str.encode(s)\nprint (type(b3))\n```\n\n{% qnimg 20171226115104.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n> bytes to str\n\n```python\nb = b'abbbb'\nprint (type(b))\n\ns = str(b, encoding = 'utf-8')\nprint (type(s))\n\n\ns2 = b.decode()\nprint (type(s2))\n\ns3 = b.decode('utf-8')\nprint (type(s3))\n\n\ns4 = bytes.decode(b)\nprint (type(s4))\n```\n\n{% qnimg 20171226115528.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n\n\n> 转载自 链接地址: http://blog.csdn.net/bible_reader/article/details/53047550\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20171219_python_error.md","raw":"---\ntitle: TypeError, a bytes-like object is required, not 'str'\n\ncomments: true\n\ntags: \n - python\n - 转码\n\ncategories: \n - BUG解决\n\ndescription: str和bytes类型之间的常用转码方式\n\ntoc: true\n\n---\n\n> 问题分析: 该问题主要是由于当前操作的字符串是bytes类型的字符串对象,并对该bytes类型的字符串对象进行按照str类型的操作。\n\n{% qnimg 20171226111345.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n* 解决办法,将s转码成为str类型\n\n{% qnimg 20171226111804.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n\n\n## str和bytes类型之间的常用转码方式\n\n> str to bytes\n\n```python\ns = 'ab bbb'\nprint (type(s))\n\nb = bytes(s, encoding = 'utf-8')\nprint (type(b))\n\nb2 = s.encode('utf-8')\nprint (type(b2))\n\nb3 = str.encode(s)\nprint (type(b3))\n```\n\n{% qnimg 20171226115104.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n> bytes to str\n\n```python\nb = b'abbbb'\nprint (type(b))\n\ns = str(b, encoding = 'utf-8')\nprint (type(s))\n\n\ns2 = b.decode()\nprint (type(s2))\n\ns3 = b.decode('utf-8')\nprint (type(s3))\n\n\ns4 = bytes.decode(b)\nprint (type(s4))\n```\n\n{% qnimg 20171226115528.png title:问题 alt:问题 extend:?imageView2/2/w/600 %}\n\n\n\n> 转载自 链接地址: http://blog.csdn.net/bible_reader/article/details/53047550\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20171219_python_error","published":1,"date":"2020-01-15T05:50:39.649Z","updated":"2021-03-10T13:50:15.268Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvl001n24ujvms6f31l","content":"
\n

问题分析: 该问题主要是由于当前操作的字符串是bytes类型的字符串对象,并对该bytes类型的字符串对象进行按照str类型的操作。

\n
\n\"问题\"\n
    \n
  • 解决办法,将s转码成为str类型
  • \n
\n\"问题\"\n\n

str和bytes类型之间的常用转码方式

\n

str to bytes

\n
\n
1
2
3
4
5
6
7
8
9
10
11
s = 'ab bbb'
print (type(s))

b = bytes(s, encoding = 'utf-8')
print (type(b))

b2 = s.encode('utf-8')
print (type(b2))

b3 = str.encode(s)
print (type(b3))
\n\"问题\"\n
\n

bytes to str

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
b = b'abbbb'
print (type(b))

s = str(b, encoding = 'utf-8')
print (type(s))


s2 = b.decode()
print (type(s2))

s3 = b.decode('utf-8')
print (type(s3))


s4 = bytes.decode(b)
print (type(s4))
\n\"问题\"\n
\n

转载自 链接地址: http://blog.csdn.net/bible_reader/article/details/53047550

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

问题分析: 该问题主要是由于当前操作的字符串是bytes类型的字符串对象,并对该bytes类型的字符串对象进行按照str类型的操作。

\n
\n\"问题\"\n
    \n
  • 解决办法,将s转码成为str类型
  • \n
\n\"问题\"","more":"

str和bytes类型之间的常用转码方式

\n

str to bytes

\n
\n
1
2
3
4
5
6
7
8
9
10
11
s = 'ab bbb'
print (type(s))

b = bytes(s, encoding = 'utf-8')
print (type(b))

b2 = s.encode('utf-8')
print (type(b2))

b3 = str.encode(s)
print (type(b3))
\n\"问题\"\n
\n

bytes to str

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
b = b'abbbb'
print (type(b))

s = str(b, encoding = 'utf-8')
print (type(s))


s2 = b.decode()
print (type(s2))

s3 = b.decode('utf-8')
print (type(s3))


s4 = bytes.decode(b)
print (type(s4))
\n\"问题\"\n
\n

转载自 链接地址: http://blog.csdn.net/bible_reader/article/details/53047550

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"Dom4j解析xml文档实现增删改查","comments":1,"description":"Dom4j解析xml文档","date":"2017-02-04T16:00:00.000Z","_content":"\n\n**这是被解析的xml文档示例**\n```xml\n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n \n```\n-------------------------------------------------------------------------\n\n\n\n\n```java\n package cn.xml;\n \n import java.io.File;\n import java.io.FileNotFoundException;\n import java.io.FileOutputStream;\n import java.io.FileWriter;\n import java.io.IOException;\n import java.io.OutputStreamWriter;\n import java.io.UnsupportedEncodingException;\n import java.util.Iterator;\n import java.util.List;\n \n import org.dom4j.Document;\n import org.dom4j.DocumentException;\n import org.dom4j.DocumentHelper;\n import org.dom4j.Element;\n import org.dom4j.io.OutputFormat;\n import org.dom4j.io.SAXReader;\n import org.dom4j.io.XMLWriter;\n import org.junit.Test;\n \n public class Dom4j {\n @Test\n public void read() throws DocumentException{//读\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element root = document.getRootElement();//得到根节点 \"书架\"\n Element book = (Element) root.elements(\"书\").get(1);//得到\"书\"节点中第二个\"书\"节点\n String value = book.element(\"书名\").getText();//得到售\"书名\"节点的内容\n String attribute = book.element(\"书名\").attribute(\"color\").getValue();//得到属性值\n String attribute1 = book.element(\"书名\").attributeValue(\"color\");//得到的同上\n \n System.out.println(value);\n System.out.println(attribute);\n System.out.println(attribute1);\n }\n \n //@Test\n public void add() throws DocumentException, IOException{//增\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element book = document.getRootElement().element(\"书\");//得到第一本书\n book.addElement(\"售价\").setText(\"45元\");//在书上添加售价节点 同时添加节点内容\n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");//设置格式化输出器的编码为UTF-8编码 使document按照utf-8格式输出\n \n //把修改写入文件 document是UTF-8编码的\n //XMLWriter writer = new XMLWriter(new FileWriter(\"src/book.xml\"));//可能出现乱码\n //OutputStreamWriter可以指点采用什么字符集编码\n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n \n //@Test\n public void add2() throws DocumentException, IOException{//在指定位置添加(通过更改保存所有孩子的List集合顺序)\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element book = document.getRootElement().element(\"书\");//得到第一本书\n List list = book.elements();//得到所有的孩子 [书名, 孩子, 售价]\n \n //创建要加入的标签 以及内容\n Element helper = DocumentHelper.createElement(\"其他\");\n helper.setText(\"内容\");\n \n //加入list集合 需要把加入位置的元素移动到下一位 然后把其加入到位置(自动)\n list.add(2, helper);//添加到第三个位置\n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");\n \n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n \n //@Test\n public void delete() throws DocumentException, IOException{//删除\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element price = document.getRootElement().element(\"书\").element(\"售价\");//得到售价节点\n price.getParent().remove(price);//用父母删孩子\n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");\n \n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n \n @Test\n public void update() throws Exception{//更新\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element book = (Element)document.getRootElement().elements(\"书\").get(1);\n book.element(\"书名\").setText(\"初日\");\n \n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");\n \n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n }\n```\n","source":"_posts/2017_Dom4j.md","raw":"---\ntitle: Dom4j解析xml文档实现增删改查\n\ncomments: true \n\ntags: Dom4j\n\ncategories: \n - DOM操作 \n - XML\n\ndescription: Dom4j解析xml文档\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n\n**这是被解析的xml文档示例**\n```xml\n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n \n```\n-------------------------------------------------------------------------\n\n\n\n\n```java\n package cn.xml;\n \n import java.io.File;\n import java.io.FileNotFoundException;\n import java.io.FileOutputStream;\n import java.io.FileWriter;\n import java.io.IOException;\n import java.io.OutputStreamWriter;\n import java.io.UnsupportedEncodingException;\n import java.util.Iterator;\n import java.util.List;\n \n import org.dom4j.Document;\n import org.dom4j.DocumentException;\n import org.dom4j.DocumentHelper;\n import org.dom4j.Element;\n import org.dom4j.io.OutputFormat;\n import org.dom4j.io.SAXReader;\n import org.dom4j.io.XMLWriter;\n import org.junit.Test;\n \n public class Dom4j {\n @Test\n public void read() throws DocumentException{//读\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element root = document.getRootElement();//得到根节点 \"书架\"\n Element book = (Element) root.elements(\"书\").get(1);//得到\"书\"节点中第二个\"书\"节点\n String value = book.element(\"书名\").getText();//得到售\"书名\"节点的内容\n String attribute = book.element(\"书名\").attribute(\"color\").getValue();//得到属性值\n String attribute1 = book.element(\"书名\").attributeValue(\"color\");//得到的同上\n \n System.out.println(value);\n System.out.println(attribute);\n System.out.println(attribute1);\n }\n \n //@Test\n public void add() throws DocumentException, IOException{//增\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element book = document.getRootElement().element(\"书\");//得到第一本书\n book.addElement(\"售价\").setText(\"45元\");//在书上添加售价节点 同时添加节点内容\n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");//设置格式化输出器的编码为UTF-8编码 使document按照utf-8格式输出\n \n //把修改写入文件 document是UTF-8编码的\n //XMLWriter writer = new XMLWriter(new FileWriter(\"src/book.xml\"));//可能出现乱码\n //OutputStreamWriter可以指点采用什么字符集编码\n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n \n //@Test\n public void add2() throws DocumentException, IOException{//在指定位置添加(通过更改保存所有孩子的List集合顺序)\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element book = document.getRootElement().element(\"书\");//得到第一本书\n List list = book.elements();//得到所有的孩子 [书名, 孩子, 售价]\n \n //创建要加入的标签 以及内容\n Element helper = DocumentHelper.createElement(\"其他\");\n helper.setText(\"内容\");\n \n //加入list集合 需要把加入位置的元素移动到下一位 然后把其加入到位置(自动)\n list.add(2, helper);//添加到第三个位置\n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");\n \n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n \n //@Test\n public void delete() throws DocumentException, IOException{//删除\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element price = document.getRootElement().element(\"书\").element(\"售价\");//得到售价节点\n price.getParent().remove(price);//用父母删孩子\n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");\n \n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n \n @Test\n public void update() throws Exception{//更新\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n Element book = (Element)document.getRootElement().elements(\"书\").get(1);\n book.element(\"书名\").setText(\"初日\");\n \n \n OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器\n format.setEncoding(\"UTF-8\");\n \n XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);\n writer.write(document);//把document对象写入\n writer.close();//关闭流\n }\n }\n```\n","slug":"2017_Dom4j","published":1,"updated":"2018-01-13T02:29:22.218Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvn001q24ujn7o1h1rg","content":"

这是被解析的xml文档示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<书架>
<>
<书名>java实战</书名>
<作者>张三</作者>
<售价>121元</售价>
<售价>12元</售价>
</>
<>
<书名 color=\"yellow\" name=\"XXX\">c测试</书名>
<作者>李四</作者>
<售价 color=\"rrr\">54元</售价>
<售价>12元</售价>
</>
</书架>

\n
\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package cn.xml;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.junit.Test;

public class Dom4j {
@Test
public void read() throws DocumentException{//读
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element root = document.getRootElement();//得到根节点 \"书架\"
Element book = (Element) root.elements(\"书\").get(1);//得到\"书\"节点中第二个\"书\"节点
String value = book.element(\"书名\").getText();//得到售\"书名\"节点的内容
String attribute = book.element(\"书名\").attribute(\"color\").getValue();//得到属性值
String attribute1 = book.element(\"书名\").attributeValue(\"color\");//得到的同上

System.out.println(value);
System.out.println(attribute);
System.out.println(attribute1);
}

//@Test
public void add() throws DocumentException, IOException{//增
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element book = document.getRootElement().element(\"书\");//得到第一本书
book.addElement(\"售价\").setText(\"45元\");//在书上添加售价节点 同时添加节点内容

OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");//设置格式化输出器的编码为UTF-8编码 使document按照utf-8格式输出

//把修改写入文件 document是UTF-8编码的
//XMLWriter writer = new XMLWriter(new FileWriter(\"src/book.xml\"));//可能出现乱码
//OutputStreamWriter可以指点采用什么字符集编码
XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}

//@Test
public void add2() throws DocumentException, IOException{//在指定位置添加(通过更改保存所有孩子的List集合顺序)
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element book = document.getRootElement().element(\"书\");//得到第一本书
List list = book.elements();//得到所有的孩子 [书名, 孩子, 售价]

//创建要加入的标签 以及内容
Element helper = DocumentHelper.createElement(\"其他\");
helper.setText(\"内容\");

//加入list集合 需要把加入位置的元素移动到下一位 然后把其加入到位置(自动)
list.add(2, helper);//添加到第三个位置

OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");

XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}

//@Test
public void delete() throws DocumentException, IOException{//删除
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element price = document.getRootElement().element(\"书\").element(\"售价\");//得到售价节点
price.getParent().remove(price);//用父母删孩子

OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");

XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}

@Test
public void update() throws Exception{//更新
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element book = (Element)document.getRootElement().elements(\"书\").get(1);
book.element(\"书名\").setText(\"初日\");


OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");

XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}
}
\n","site":{"data":{}},"excerpt":"

这是被解析的xml文档示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<书架>
<>
<书名>java实战</书名>
<作者>张三</作者>
<售价>121元</售价>
<售价>12元</售价>
</>
<>
<书名 color=\"yellow\" name=\"XXX\">c测试</书名>
<作者>李四</作者>
<售价 color=\"rrr\">54元</售价>
<售价>12元</售价>
</>
</书架>

\n
","more":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package cn.xml;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.junit.Test;

public class Dom4j {
@Test
public void read() throws DocumentException{//读
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element root = document.getRootElement();//得到根节点 \"书架\"
Element book = (Element) root.elements(\"书\").get(1);//得到\"书\"节点中第二个\"书\"节点
String value = book.element(\"书名\").getText();//得到售\"书名\"节点的内容
String attribute = book.element(\"书名\").attribute(\"color\").getValue();//得到属性值
String attribute1 = book.element(\"书名\").attributeValue(\"color\");//得到的同上

System.out.println(value);
System.out.println(attribute);
System.out.println(attribute1);
}

//@Test
public void add() throws DocumentException, IOException{//增
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element book = document.getRootElement().element(\"书\");//得到第一本书
book.addElement(\"售价\").setText(\"45元\");//在书上添加售价节点 同时添加节点内容

OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");//设置格式化输出器的编码为UTF-8编码 使document按照utf-8格式输出

//把修改写入文件 document是UTF-8编码的
//XMLWriter writer = new XMLWriter(new FileWriter(\"src/book.xml\"));//可能出现乱码
//OutputStreamWriter可以指点采用什么字符集编码
XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}

//@Test
public void add2() throws DocumentException, IOException{//在指定位置添加(通过更改保存所有孩子的List集合顺序)
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element book = document.getRootElement().element(\"书\");//得到第一本书
List list = book.elements();//得到所有的孩子 [书名, 孩子, 售价]

//创建要加入的标签 以及内容
Element helper = DocumentHelper.createElement(\"其他\");
helper.setText(\"内容\");

//加入list集合 需要把加入位置的元素移动到下一位 然后把其加入到位置(自动)
list.add(2, helper);//添加到第三个位置

OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");

XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}

//@Test
public void delete() throws DocumentException, IOException{//删除
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element price = document.getRootElement().element(\"书\").element(\"售价\");//得到售价节点
price.getParent().remove(price);//用父母删孩子

OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");

XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}

@Test
public void update() throws Exception{//更新
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

Element book = (Element)document.getRootElement().elements(\"书\").get(1);
book.element(\"书名\").setText(\"初日\");


OutputFormat format = new OutputFormat().createPrettyPrint();//格式化输出器
format.setEncoding(\"UTF-8\");

XMLWriter writer = new XMLWriter(new FileOutputStream(\"src/book.xml\"), format);
writer.write(document);//把document对象写入
writer.close();//关闭流
}
}
"},{"title":"正则表达式","comments":1,"description":null,"toc":true,"_content":"\n## 正则表达式: waiting。。。\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20171225_regular.md","raw":"---\ntitle: 正则表达式\n\ncomments: true \n\ntags: \n - java\n - python\n - 正则表达式\n\ncategories: \n - 正则\n\ndescription: \n\ntoc: true\n \n---\n\n## 正则表达式: waiting。。。\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20171225_regular","published":1,"date":"2020-01-15T05:50:39.652Z","updated":"2021-03-10T13:50:15.287Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvp001v24uj7islvtp8","content":"

正则表达式: waiting。。。

\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"","more":"

正则表达式: waiting。。。

\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n"},{"title":"2.Java运行时数据区","comments":1,"description":null,"date":"2017-02-04T16:00:00.000Z","_content":"\n## *概述* : \n#### Java将new的对象的管理权交由Java虚拟机,so对于Java程序员,不需要写配套的delete/free代码,不容易出现内存溢出或泄露,但是一旦出现了,就需要了解虚拟机如何使用内存来排除bug\n----------\n#### Java虚拟机在执行程序时会把管理的内存划分为多个不同的数据区域,\n![这里写图片描述](http://img.blog.csdn.net/20161215182730152?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW55YW8xMTIyMzM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n----------\n\n### **1.程序计数器**--->*线程私有的区域*\n#### 占用内存较小,作用是**作为当前线程执行的字节码的行号指示器**。\n#### 在概念模型里,字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程恢复等基础功能都需要此计数器完成。\n#### Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,so每次处理器只执行一个线程。 so **每个线程都拥有一个独立的程序计数器**,各线程计数器互不影响,独立储存。\n#### 若执行的是Native方法,则程序计数器值为空(Undefined) <------唯一\n\n\n\n\n----------\n\n### **2.Java虚拟机栈**--->*线程私有的区域*\n#### 描述了Java方法执行的内存模式,每个方法在执行同时会创建一个栈帧,用来存储\n\n 1. **局部变量表** (编译时即确定内存大小,方法执行时不会改变其大小)\n 1.1. 存放编译期各种可知的基本数据类型 (64位长度的long,double占两个局部变量空间,其他占一个)\n 1.2. 对象引用 (指向对象其实地址的指针或是指向代表对象的句柄或其他与此对象相关的位置)\n 1.3. returnAddress类型 (指向一个字节码指令的地址)\n 2. **操作数栈**\n 3. **动态链接**\n 4. **方法出口**\n\n#### **方法的执行就是栈帧在虚拟机栈中的入栈到出栈的过程**。\n#### 人们常说的Java内存分配的堆内存(Heap),栈内存(Stack) 中 后者就是这里的虚拟机栈。\n#### 当线程请求的栈深度大于虚拟机所允许的深度 --------- *StackOverflowError*(当然,目前虚拟机栈都是可以动态扩展的)\n#### 当无法申请到足够的内存 --------------------*OutOfMemoryError*\n----------\n\n### **3.本地方法栈**(Native Method Stack)\n#### 功能与虚拟机栈相似\n\n - 虚拟机栈:为虚拟机执行Java方法(字节码)服务\n - 本地方法栈:为虚拟机使用的Native方法服务 \n - ----------\n\n### **4.Java堆**--->*所有线程共享*\n#### 内存最大,虚拟机启动时创建, Java垃圾收集器主要管理区域(也叫**GC堆**)\n#### 作用:**存放对象实例(和数组)** 即为对象实例分配内存\n#### 可以进行进一步划分多个线程私有的分配缓冲区\n#### 可以处于物理不连续的内存空间,但逻辑连续;可以实现成扩展,也可以固定(可通过-Xmx -Xms控制)\n - ----------\n### **5.方法区**--->*所有线程共享*\n#### 用于**存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等**\n#### 别名 Non-Heap \n#### 可以物理内存不连续, 可以固定或可扩展, 可选择不实现垃圾收集(需要对常量池回收和对类型进行卸载,否则 易出现内存泄漏)\n - ----------\n\n### **6.运行时常量池(Runtime Constant Pool)**\n#### 方法区的一部分,用于**存储编译期生成的各种字面符号引用(一般也存储翻译出来的直接引用)**\n#### Java对Class文件每部分(类版本,字段,方法,接口,常量池等)格式有严格规定,符合规定才能被虚拟机认可,装载,执行。but对运行时常量池,无规定。\n#### 具有**动态性**(Class文件常量池无动态性) \n - ----------\n\n### **7.直接内存(堆外内存)**\n#### 不是虚拟机运行时数据区的一部分,不归虚拟机管理\n#### 减少了垃圾回收的工作(垃圾回收会暂停其他的工作)\n#### 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。 \n#### 堆外内存难以控制,如果内存泄漏,那么很难排查 \n#### 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。\n此处来源:http://blog.csdn.net/qq_17612199/article/details/52316719","source":"_posts/2017_JAVA_runtime.md","raw":"---\ntitle: 2.Java运行时数据区\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n## *概述* : \n#### Java将new的对象的管理权交由Java虚拟机,so对于Java程序员,不需要写配套的delete/free代码,不容易出现内存溢出或泄露,但是一旦出现了,就需要了解虚拟机如何使用内存来排除bug\n----------\n#### Java虚拟机在执行程序时会把管理的内存划分为多个不同的数据区域,\n![这里写图片描述](http://img.blog.csdn.net/20161215182730152?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW55YW8xMTIyMzM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n----------\n\n### **1.程序计数器**--->*线程私有的区域*\n#### 占用内存较小,作用是**作为当前线程执行的字节码的行号指示器**。\n#### 在概念模型里,字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程恢复等基础功能都需要此计数器完成。\n#### Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,so每次处理器只执行一个线程。 so **每个线程都拥有一个独立的程序计数器**,各线程计数器互不影响,独立储存。\n#### 若执行的是Native方法,则程序计数器值为空(Undefined) <------唯一\n\n\n\n\n----------\n\n### **2.Java虚拟机栈**--->*线程私有的区域*\n#### 描述了Java方法执行的内存模式,每个方法在执行同时会创建一个栈帧,用来存储\n\n 1. **局部变量表** (编译时即确定内存大小,方法执行时不会改变其大小)\n 1.1. 存放编译期各种可知的基本数据类型 (64位长度的long,double占两个局部变量空间,其他占一个)\n 1.2. 对象引用 (指向对象其实地址的指针或是指向代表对象的句柄或其他与此对象相关的位置)\n 1.3. returnAddress类型 (指向一个字节码指令的地址)\n 2. **操作数栈**\n 3. **动态链接**\n 4. **方法出口**\n\n#### **方法的执行就是栈帧在虚拟机栈中的入栈到出栈的过程**。\n#### 人们常说的Java内存分配的堆内存(Heap),栈内存(Stack) 中 后者就是这里的虚拟机栈。\n#### 当线程请求的栈深度大于虚拟机所允许的深度 --------- *StackOverflowError*(当然,目前虚拟机栈都是可以动态扩展的)\n#### 当无法申请到足够的内存 --------------------*OutOfMemoryError*\n----------\n\n### **3.本地方法栈**(Native Method Stack)\n#### 功能与虚拟机栈相似\n\n - 虚拟机栈:为虚拟机执行Java方法(字节码)服务\n - 本地方法栈:为虚拟机使用的Native方法服务 \n - ----------\n\n### **4.Java堆**--->*所有线程共享*\n#### 内存最大,虚拟机启动时创建, Java垃圾收集器主要管理区域(也叫**GC堆**)\n#### 作用:**存放对象实例(和数组)** 即为对象实例分配内存\n#### 可以进行进一步划分多个线程私有的分配缓冲区\n#### 可以处于物理不连续的内存空间,但逻辑连续;可以实现成扩展,也可以固定(可通过-Xmx -Xms控制)\n - ----------\n### **5.方法区**--->*所有线程共享*\n#### 用于**存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等**\n#### 别名 Non-Heap \n#### 可以物理内存不连续, 可以固定或可扩展, 可选择不实现垃圾收集(需要对常量池回收和对类型进行卸载,否则 易出现内存泄漏)\n - ----------\n\n### **6.运行时常量池(Runtime Constant Pool)**\n#### 方法区的一部分,用于**存储编译期生成的各种字面符号引用(一般也存储翻译出来的直接引用)**\n#### Java对Class文件每部分(类版本,字段,方法,接口,常量池等)格式有严格规定,符合规定才能被虚拟机认可,装载,执行。but对运行时常量池,无规定。\n#### 具有**动态性**(Class文件常量池无动态性) \n - ----------\n\n### **7.直接内存(堆外内存)**\n#### 不是虚拟机运行时数据区的一部分,不归虚拟机管理\n#### 减少了垃圾回收的工作(垃圾回收会暂停其他的工作)\n#### 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。 \n#### 堆外内存难以控制,如果内存泄漏,那么很难排查 \n#### 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。\n此处来源:http://blog.csdn.net/qq_17612199/article/details/52316719","slug":"2017_JAVA_runtime","published":1,"updated":"2018-01-13T02:29:22.224Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvr001y24ujmua3zeiu","content":"

概述 :

Java将new的对象的管理权交由Java虚拟机,so对于Java程序员,不需要写配套的delete/free代码,不容易出现内存溢出或泄露,但是一旦出现了,就需要了解虚拟机如何使用内存来排除bug


\n

Java虚拟机在执行程序时会把管理的内存划分为多个不同的数据区域,

\"这里写图片描述\"

\n
\n

1.程序计数器—>线程私有的区域

占用内存较小,作用是作为当前线程执行的字节码的行号指示器

在概念模型里,字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程恢复等基础功能都需要此计数器完成。

Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,so每次处理器只执行一个线程。 so 每个线程都拥有一个独立的程序计数器,各线程计数器互不影响,独立储存。

若执行的是Native方法,则程序计数器值为空(Undefined) <——唯一

\n
\n

2.Java虚拟机栈—>线程私有的区域

描述了Java方法执行的内存模式,每个方法在执行同时会创建一个栈帧,用来存储

    \n
  1. 局部变量表 (编译时即确定内存大小,方法执行时不会改变其大小)
    1.1. 存放编译期各种可知的基本数据类型 (64位长度的long,double占两个局部变量空间,其他占一个)
    1.2. 对象引用 (指向对象其实地址的指针或是指向代表对象的句柄或其他与此对象相关的位置)
    1.3. returnAddress类型 (指向一个字节码指令的地址)
  2. \n
  3. 操作数栈
  4. \n
  5. 动态链接
  6. \n
  7. 方法出口
  8. \n
\n

方法的执行就是栈帧在虚拟机栈中的入栈到出栈的过程

人们常说的Java内存分配的堆内存(Heap),栈内存(Stack) 中 后者就是这里的虚拟机栈。

当线程请求的栈深度大于虚拟机所允许的深度 ——— StackOverflowError(当然,目前虚拟机栈都是可以动态扩展的)

当无法申请到足够的内存 ——————–OutOfMemoryError


\n

3.本地方法栈(Native Method Stack)

功能与虚拟机栈相似

    \n
  • 虚拟机栈:为虚拟机执行Java方法(字节码)服务
  • \n
  • 本地方法栈:为虚拟机使用的Native方法服务
  • \n
\n
\n

4.Java堆—>所有线程共享

内存最大,虚拟机启动时创建, Java垃圾收集器主要管理区域(也叫GC堆

作用:存放对象实例(和数组) 即为对象实例分配内存

可以进行进一步划分多个线程私有的分配缓冲区

可以处于物理不连续的内存空间,但逻辑连续;可以实现成扩展,也可以固定(可通过-Xmx -Xms控制)


\n

5.方法区—>所有线程共享

用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等

别名 Non-Heap

可以物理内存不连续, 可以固定或可扩展, 可选择不实现垃圾收集(需要对常量池回收和对类型进行卸载,否则 易出现内存泄漏)


\n

6.运行时常量池(Runtime Constant Pool)

方法区的一部分,用于存储编译期生成的各种字面符号引用(一般也存储翻译出来的直接引用)

Java对Class文件每部分(类版本,字段,方法,接口,常量池等)格式有严格规定,符合规定才能被虚拟机认可,装载,执行。but对运行时常量池,无规定。

具有动态性(Class文件常量池无动态性)


\n

7.直接内存(堆外内存)

不是虚拟机运行时数据区的一部分,不归虚拟机管理

减少了垃圾回收的工作(垃圾回收会暂停其他的工作)

加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。

堆外内存难以控制,如果内存泄漏,那么很难排查

堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。

此处来源:http://blog.csdn.net/qq_17612199/article/details/52316719

\n","site":{"data":{}},"excerpt":"

概述 :

Java将new的对象的管理权交由Java虚拟机,so对于Java程序员,不需要写配套的delete/free代码,不容易出现内存溢出或泄露,但是一旦出现了,就需要了解虚拟机如何使用内存来排除bug


\n

Java虚拟机在执行程序时会把管理的内存划分为多个不同的数据区域,

\"这里写图片描述\"

\n
\n

1.程序计数器—>线程私有的区域

占用内存较小,作用是作为当前线程执行的字节码的行号指示器

在概念模型里,字节码解释器工作时通过改变程序计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程恢复等基础功能都需要此计数器完成。

Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,so每次处理器只执行一个线程。 so 每个线程都拥有一个独立的程序计数器,各线程计数器互不影响,独立储存。

若执行的是Native方法,则程序计数器值为空(Undefined) <——唯一

","more":"
\n

2.Java虚拟机栈—>线程私有的区域

描述了Java方法执行的内存模式,每个方法在执行同时会创建一个栈帧,用来存储

    \n
  1. 局部变量表 (编译时即确定内存大小,方法执行时不会改变其大小)
    1.1. 存放编译期各种可知的基本数据类型 (64位长度的long,double占两个局部变量空间,其他占一个)
    1.2. 对象引用 (指向对象其实地址的指针或是指向代表对象的句柄或其他与此对象相关的位置)
    1.3. returnAddress类型 (指向一个字节码指令的地址)
  2. \n
  3. 操作数栈
  4. \n
  5. 动态链接
  6. \n
  7. 方法出口
  8. \n
\n

方法的执行就是栈帧在虚拟机栈中的入栈到出栈的过程

人们常说的Java内存分配的堆内存(Heap),栈内存(Stack) 中 后者就是这里的虚拟机栈。

当线程请求的栈深度大于虚拟机所允许的深度 ——— StackOverflowError(当然,目前虚拟机栈都是可以动态扩展的)

当无法申请到足够的内存 ——————–OutOfMemoryError


\n

3.本地方法栈(Native Method Stack)

功能与虚拟机栈相似

    \n
  • 虚拟机栈:为虚拟机执行Java方法(字节码)服务
  • \n
  • 本地方法栈:为虚拟机使用的Native方法服务
  • \n
\n
\n

4.Java堆—>所有线程共享

内存最大,虚拟机启动时创建, Java垃圾收集器主要管理区域(也叫GC堆

作用:存放对象实例(和数组) 即为对象实例分配内存

可以进行进一步划分多个线程私有的分配缓冲区

可以处于物理不连续的内存空间,但逻辑连续;可以实现成扩展,也可以固定(可通过-Xmx -Xms控制)


\n

5.方法区—>所有线程共享

用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等

别名 Non-Heap

可以物理内存不连续, 可以固定或可扩展, 可选择不实现垃圾收集(需要对常量池回收和对类型进行卸载,否则 易出现内存泄漏)


\n

6.运行时常量池(Runtime Constant Pool)

方法区的一部分,用于存储编译期生成的各种字面符号引用(一般也存储翻译出来的直接引用)

Java对Class文件每部分(类版本,字段,方法,接口,常量池等)格式有严格规定,符合规定才能被虚拟机认可,装载,执行。but对运行时常量池,无规定。

具有动态性(Class文件常量池无动态性)


\n

7.直接内存(堆外内存)

不是虚拟机运行时数据区的一部分,不归虚拟机管理

减少了垃圾回收的工作(垃圾回收会暂停其他的工作)

加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后在发送;而堆外内存相当于省略掉了这个工作。

堆外内存难以控制,如果内存泄漏,那么很难排查

堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。

此处来源:http://blog.csdn.net/qq_17612199/article/details/52316719

"},{"title":"3.HotSpot虚拟机 对象创建","comments":1,"description":null,"date":"2017-02-04T16:00:00.000Z","_content":"\n## **对象创建**\n\n 1. new指令 (new一个对象)\n 2. 检查这个指令的参数是否能在常量池中定位到一个类符号引用\n 3. 检查此符号引用代表的类是否被加载,解析,初始化过\n 4. 否:执行相应的类加载\n 5. 是(类被加载过后),虚拟机为新生的对象分配内存(大小在类加载时确定),把某大小的堆内存划分给此对象\n \n 5.1. 如果堆内存整齐划分:分配内存实质是把指向空闲内存的指针移动该对象大小相等的位置,这种分配方式叫**指针碰壁**\n \n 5.2 如果堆内存空闲内存与非空闲随机:则虚拟机会维护一个内存列表,每次分配都划分相应的内存给对象,这种分配方式叫**空闲列表**\n \n 6. 分配方式决定于Java堆是否规整,Java堆是否规整决定于采用的垃圾回收器是否有压缩整理功能。\n \n 6.1 并发情况下,对于修改指针指向位置,有两种方案\n \n 1.对分配内存空间进行同步处理--实际上,虚拟机采用CAS配上失败重试的方法保证更新操作的原子性\n 2.把内存的分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存(称为本地线程分配缓冲TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,当TLAB用完需要分配新的TLAB时才同步锁定 ----- 可以通过-XX:+/-UseTLAB参数设定\n 7. 内存分配完毕,虚拟机把分配的内存初始化为零值(不包括对象头),使用TLAB则此可以在TLAB前执行。此步骤保证了对象的实例字段在代码中不赋初值即可使用,程序可访问到这些字段的数据类型所对应的默认初始值\n 8. 虚拟机对对象进行必要的设置(指明类,找到类的元数据信息,对象的哈希码等)这些存放在对象的对象头(Object Header)中。 \n 9. **对于虚拟机,新的对象已经产生。对于程序,才刚刚开始**\n 10. 执行< init >方法,把对象按照程序员意愿进行初始化。可以用的对象新建成功\n","source":"_posts/2017_HotSpot.md","raw":"---\ntitle: 3.HotSpot虚拟机 对象创建\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n## **对象创建**\n\n 1. new指令 (new一个对象)\n 2. 检查这个指令的参数是否能在常量池中定位到一个类符号引用\n 3. 检查此符号引用代表的类是否被加载,解析,初始化过\n 4. 否:执行相应的类加载\n 5. 是(类被加载过后),虚拟机为新生的对象分配内存(大小在类加载时确定),把某大小的堆内存划分给此对象\n \n 5.1. 如果堆内存整齐划分:分配内存实质是把指向空闲内存的指针移动该对象大小相等的位置,这种分配方式叫**指针碰壁**\n \n 5.2 如果堆内存空闲内存与非空闲随机:则虚拟机会维护一个内存列表,每次分配都划分相应的内存给对象,这种分配方式叫**空闲列表**\n \n 6. 分配方式决定于Java堆是否规整,Java堆是否规整决定于采用的垃圾回收器是否有压缩整理功能。\n \n 6.1 并发情况下,对于修改指针指向位置,有两种方案\n \n 1.对分配内存空间进行同步处理--实际上,虚拟机采用CAS配上失败重试的方法保证更新操作的原子性\n 2.把内存的分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存(称为本地线程分配缓冲TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,当TLAB用完需要分配新的TLAB时才同步锁定 ----- 可以通过-XX:+/-UseTLAB参数设定\n 7. 内存分配完毕,虚拟机把分配的内存初始化为零值(不包括对象头),使用TLAB则此可以在TLAB前执行。此步骤保证了对象的实例字段在代码中不赋初值即可使用,程序可访问到这些字段的数据类型所对应的默认初始值\n 8. 虚拟机对对象进行必要的设置(指明类,找到类的元数据信息,对象的哈希码等)这些存放在对象的对象头(Object Header)中。 \n 9. **对于虚拟机,新的对象已经产生。对于程序,才刚刚开始**\n 10. 执行< init >方法,把对象按照程序员意愿进行初始化。可以用的对象新建成功\n","slug":"2017_HotSpot","published":1,"updated":"2018-01-13T02:29:22.221Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvu002324ujqgseuf32","content":"

对象创建

    \n
  1. new指令 (new一个对象)
  2. \n
  3. 检查这个指令的参数是否能在常量池中定位到一个类符号引用
  4. \n
  5. 检查此符号引用代表的类是否被加载,解析,初始化过
  6. \n
  7. 否:执行相应的类加载
  8. \n
  9. 是(类被加载过后),虚拟机为新生的对象分配内存(大小在类加载时确定),把某大小的堆内存划分给此对象

    \n

    5.1. 如果堆内存整齐划分:分配内存实质是把指向空闲内存的指针移动该对象大小相等的位置,这种分配方式叫指针碰壁

    \n

    5.2 如果堆内存空闲内存与非空闲随机:则虚拟机会维护一个内存列表,每次分配都划分相应的内存给对象,这种分配方式叫空闲列表

    \n
  10. \n
  11. 分配方式决定于Java堆是否规整,Java堆是否规整决定于采用的垃圾回收器是否有压缩整理功能。

    \n

    6.1 并发情况下,对于修改指针指向位置,有两种方案

    \n
    1.对分配内存空间进行同步处理--实际上,虚拟机采用CAS配上失败重试的方法保证更新操作的原子性\n2.把内存的分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存(称为本地线程分配缓冲TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,当TLAB用完需要分配新的TLAB时才同步锁定 ----- 可以通过-XX:+/-UseTLAB参数设定\n
  12. \n
  13. 内存分配完毕,虚拟机把分配的内存初始化为零值(不包括对象头),使用TLAB则此可以在TLAB前执行。此步骤保证了对象的实例字段在代码中不赋初值即可使用,程序可访问到这些字段的数据类型所对应的默认初始值
  14. \n
  15. 虚拟机对对象进行必要的设置(指明类,找到类的元数据信息,对象的哈希码等)这些存放在对象的对象头(Object Header)中。
  16. \n
  17. 对于虚拟机,新的对象已经产生。对于程序,才刚刚开始
  18. \n
  19. 执行< init >方法,把对象按照程序员意愿进行初始化。可以用的对象新建成功
  20. \n
\n","site":{"data":{}},"excerpt":"","more":"

对象创建

    \n
  1. new指令 (new一个对象)
  2. \n
  3. 检查这个指令的参数是否能在常量池中定位到一个类符号引用
  4. \n
  5. 检查此符号引用代表的类是否被加载,解析,初始化过
  6. \n
  7. 否:执行相应的类加载
  8. \n
  9. 是(类被加载过后),虚拟机为新生的对象分配内存(大小在类加载时确定),把某大小的堆内存划分给此对象

    \n

    5.1. 如果堆内存整齐划分:分配内存实质是把指向空闲内存的指针移动该对象大小相等的位置,这种分配方式叫指针碰壁

    \n

    5.2 如果堆内存空闲内存与非空闲随机:则虚拟机会维护一个内存列表,每次分配都划分相应的内存给对象,这种分配方式叫空闲列表

    \n
  10. \n
  11. 分配方式决定于Java堆是否规整,Java堆是否规整决定于采用的垃圾回收器是否有压缩整理功能。

    \n

    6.1 并发情况下,对于修改指针指向位置,有两种方案

    \n
    1.对分配内存空间进行同步处理--实际上,虚拟机采用CAS配上失败重试的方法保证更新操作的原子性\n2.把内存的分配的动作按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存(称为本地线程分配缓冲TLAB),哪个线程需要分配内存,就在哪个线程的TLAB上分配,当TLAB用完需要分配新的TLAB时才同步锁定 ----- 可以通过-XX:+/-UseTLAB参数设定\n
  12. \n
  13. 内存分配完毕,虚拟机把分配的内存初始化为零值(不包括对象头),使用TLAB则此可以在TLAB前执行。此步骤保证了对象的实例字段在代码中不赋初值即可使用,程序可访问到这些字段的数据类型所对应的默认初始值
  14. \n
  15. 虚拟机对对象进行必要的设置(指明类,找到类的元数据信息,对象的哈希码等)这些存放在对象的对象头(Object Header)中。
  16. \n
  17. 对于虚拟机,新的对象已经产生。对于程序,才刚刚开始
  18. \n
  19. 执行< init >方法,把对象按照程序员意愿进行初始化。可以用的对象新建成功
  20. \n
\n"},{"title":"Linux命令","comments":1,"description":null,"date":"2017-02-08T16:00:00.000Z","_content":"\n\n\n*不停更新ing* 贴一个搜索命令的网站 http://man.linuxde.net/\n# **1.各版本Linux安装命令**\n\n---\n\n\n# **2.目录处理及文件处理命令**\n\n---\n\n# **3.帮助命令**\n\n---\n\n# **4.压缩命令**\n\n---\n\n# **5.关机,重启与用户查看命令**\n\n---\n\n# **6.挂载命令**\n\n---\n\n# **7.挂载命令**\n\n---\n\n# **8.Shell常用命令**\n## **1.执行脚本**\n## **2.别名与快捷键**\n## **3.历史命令**\n## **4.输出重定向**\n## **5.管道符**\n## **6.管通配**\n\n---","source":"_posts/2017_Linux.md","raw":"---\ntitle: Linux命令\n\ncomments: true \n\ntags: \n - 常用命令\n - shell\n\ncategories: \n - Linux\n\ndescription: \n\ndate: 2017-02-09\n \n---\n\n\n\n*不停更新ing* 贴一个搜索命令的网站 http://man.linuxde.net/\n# **1.各版本Linux安装命令**\n\n---\n\n\n# **2.目录处理及文件处理命令**\n\n---\n\n# **3.帮助命令**\n\n---\n\n# **4.压缩命令**\n\n---\n\n# **5.关机,重启与用户查看命令**\n\n---\n\n# **6.挂载命令**\n\n---\n\n# **7.挂载命令**\n\n---\n\n# **8.Shell常用命令**\n## **1.执行脚本**\n## **2.别名与快捷键**\n## **3.历史命令**\n## **4.输出重定向**\n## **5.管道符**\n## **6.管通配**\n\n---","slug":"2017_Linux","published":1,"updated":"2018-01-13T02:29:22.226Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvv002624ujmy4899ze","content":"\n

不停更新ing 贴一个搜索命令的网站 http://man.linuxde.net/

\n

1.各版本Linux安装命令


\n

2.目录处理及文件处理命令


\n

3.帮助命令


\n

4.压缩命令


\n

5.关机,重启与用户查看命令


\n

6.挂载命令


\n

7.挂载命令


\n

8.Shell常用命令

1.执行脚本

2.别名与快捷键

3.历史命令

4.输出重定向

5.管道符

6.管通配


\n","site":{"data":{}},"excerpt":"","more":"

不停更新ing 贴一个搜索命令的网站 http://man.linuxde.net/

\n

1.各版本Linux安装命令


\n

2.目录处理及文件处理命令


\n

3.帮助命令


\n

4.压缩命令


\n

5.关机,重启与用户查看命令


\n

6.挂载命令


\n

7.挂载命令


\n

8.Shell常用命令

1.执行脚本

2.别名与快捷键

3.历史命令

4.输出重定向

5.管道符

6.管通配


"},{"title":"Mongodb安装与启动","comments":1,"description":"Mongodb的安装,配置,启动以及部分常见问题的解决方法","date":"2017-02-28T16:00:00.000Z","_content":"\n## **MongoDB** 是一个基于分布式文件存储的数据库。由C++语言编写;是一个介于关系数据库和非关系数据库之间的产品\n\n## 其支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。\n\n# 使用原理 :\n>**面向集合**: 数据被分组存储在数据集中,被称为一个集合(Collection),每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档 (类似于传统数据库中的 表(table))\n\n>**模式自由**: 意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义;存储在集合中的文档,\n\n> **键-值对的形式**:键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized Document Format)\n\n\n\n# 安装\n1. 环境:CentOs6.5 64位 远程工具 xsell\n2. 下载:官网下载 https://www.mongodb.com/download-center?jmp=nav#community 或者\n\t输入命令: > curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz\n\t\n3. 解压: > tar -zxvf mongodb-linux-x86_64-3.0.6.tgz\n4. 移动到目录 mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb\n5. 把bin目录添加到环境变量PATH中 : vim /etc/profile\n\t添加或修改为: export PATH=\"/usr/local/mongodb/bin:$PATH\"\n6. 配置自己的数据,日志等目录\n\tI. > cd /usr/local/mongodb\n\tII. > mkdir data\n\tIII. > mkdir log\n\tIV. > mkdir conf\n\tV. > cd conf\n\tVI. > touch mongodb.conf\n\tVII. > vim mongodb.conf\n\t\n\t\n port = 27017\n dbpath = data\n logpath = log/mongod.log\n fork = true\n\n\n# 启动\n7. 配置完毕 启动服务 \n\t1. 可以使用自己的配置文件中的配置 /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/conf/mongodb.conf\n\t2. 也可以输入配置目录启动 /usr/local/mongodb/bin/mongod --dbpath=/usr/local/mongodb/data/ --port=12345 --fork --logpath=/usr/local/mongodb/log/mongodb.log\n8. 启动之后 输入 > mongo 127.0.0.1:12345/admin 连接mongodb服务\n\t目前没有设置用户名密码 所以需要无认证启动, so先设置用户名密码\n9. mongodb中用户是归属于数据库的 ,可以说是为数据库设置自己的用户,并设置权限,一般一个用户只是管理一个数据库\n\t(当然,可以设置一个超级管理员用来管理所有的数据库)\n\t*下面的意思是为admin数据库设置一个用户名为\"root\",密码为\"root\"的用户,用户权限(角色) 是root(超级管理员)*\n\n\n > use admin\n > db.createUser(\n ... {\n ... user: \"root\",\n ... pwd: \"root\",\n ... roles: [ { role: \"root\", db: \"admin\" } ]\n ... }\n ... )\n \n \n*上面是mongodb3.0的新建用户方式, 2.x的方式有所不同,自行查阅*\n\n下面是mongodb内置的角色\n\n\n 1. 数据库用户角色:read、readWrite;\n 2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;\n 3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;\n 4. 备份恢复角色:backup、restore;\n 5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase\n 6. 超级用户角色:root \n // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)\n 7. 内部角色:__system\n\t\n\t\nRead:允许用户读取指定数据库\nreadWrite:允许用户读写指定数据库\ndbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile\nuserAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户\nclusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。\nreadAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限\nreadWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限\nuserAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限\ndbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。\nroot:只在admin数据库中可用。超级账号,超级权限\n\n\n\n 也可以创建角色:\n\n \n > db.createRole(\n ... {\n ... role: \"manageOpRole\",\n ... privileges: [ \n ... { resource: { cluster: true }, actions: [ \"killop\", \"inprog\" ] }, \n ... { resource: { db: \"my_test\", collection: \"my_collection\" }, \n ... actions: [ \"insert\", \"update\", \"remove\", \"compact\"] } \n ... ],\n ... roles: [] \n ... }\n ... )\n \n {\n \"role\" : \"manageOpRole\",\n \"privileges\" : [\n {\n \"resource\" : {\n \"cluster\" : true\n },\n \"actions\" : [\n \"killop\",\n \"inprog\"\n ]\n },\n {\n \"resource\" : {\n \"db\" : \"my_test\",\n \"collection\" : \"my_collection\"\n },\n \"actions\" : [\n \"insert\", \n \"update\", \n \"remove\", \n \"compact\"\n ]\n }\n ],\n \"roles\" : [ ]\n }\n > \n\n\n*可以添加几个其他角色的用户来测试权限*\n\n10. 要用用户登录的服务 so先关闭服务: > db.shutdownServer()\n\n11. 启动带权限验证的mongodb服务: \n\n\n > /usr/local/mongodb/bin/mongod --dbpath=/usr/local/mongodb/data/ --port=12345 --fork --logpath=/usr/local/mongodb/log/mongodb.log -auth\n \n \n\t如果报错too many positional options是由于--的原因,需要写英文的两个-\n12. 连接 > mongo 127.0.0.1:12345/admin\n13. 使用数据库 use admin\n14. 进行一些数据库操作 比如 > show dbs 此时就会报错 用用户名密码验证权限\n\n \n > db.auth('root','anyao112233')\n \n \n\t返回1表示成功 返回0表示失败 ; 此时再输入:\n\t\n> show dbs\n> show collections\n\n\n\t就会返回正常结果;\n\n\n# 常见问题解决\n##### *注意* : > use test //用来切换别的数据库\n##### 此时如果登录的用户没有操作此数据库的权限 show dbs就会报错\n\n\t#####*注意* : 此时关闭服务> db.shutdownServer() 可能会报错,它提示没有shutdown的权限\n\t\t解决方法: > db.grantRolesToUser( \"root\" , [ { role: \"hostManager\", db: \"admin\" } ])\n\n\t为用户root赋予hostManager角色的权限,然后就可以关闭了\n\t\n15. 输入> exit 退出界面\n\n\t*注*:可以用浏览器访问 127.0.0.1:27017\n\t*注*:有时候shutdown以后 无法再启动 报错\n\t\t原因:1. mongodb没有正常关闭 \n\t\t\t\t解决方法:删除mongodb的data目录下的mongod.lock (不能解决就把log目录中日志删除)\n\t\t\t 2.上面试了还是无法启动,那就是mongodb服务可能没有访问data,log等目录的权限\n\t\t\t \t解决方法:> chmod -R 777 /usr/local/mongodb/\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","source":"_posts/2017_Mongodb.md","raw":"---\ntitle: Mongodb安装与启动\n\ncomments: true \n\ntags: \n - Mongodb\n\ncategories: \n - 数据库\n\ndescription: Mongodb的安装,配置,启动以及部分常见问题的解决方法\n\ndate: 2017-03-1\n \n---\n\n## **MongoDB** 是一个基于分布式文件存储的数据库。由C++语言编写;是一个介于关系数据库和非关系数据库之间的产品\n\n## 其支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。\n\n# 使用原理 :\n>**面向集合**: 数据被分组存储在数据集中,被称为一个集合(Collection),每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档 (类似于传统数据库中的 表(table))\n\n>**模式自由**: 意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义;存储在集合中的文档,\n\n> **键-值对的形式**:键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized Document Format)\n\n\n\n# 安装\n1. 环境:CentOs6.5 64位 远程工具 xsell\n2. 下载:官网下载 https://www.mongodb.com/download-center?jmp=nav#community 或者\n\t输入命令: > curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz\n\t\n3. 解压: > tar -zxvf mongodb-linux-x86_64-3.0.6.tgz\n4. 移动到目录 mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb\n5. 把bin目录添加到环境变量PATH中 : vim /etc/profile\n\t添加或修改为: export PATH=\"/usr/local/mongodb/bin:$PATH\"\n6. 配置自己的数据,日志等目录\n\tI. > cd /usr/local/mongodb\n\tII. > mkdir data\n\tIII. > mkdir log\n\tIV. > mkdir conf\n\tV. > cd conf\n\tVI. > touch mongodb.conf\n\tVII. > vim mongodb.conf\n\t\n\t\n port = 27017\n dbpath = data\n logpath = log/mongod.log\n fork = true\n\n\n# 启动\n7. 配置完毕 启动服务 \n\t1. 可以使用自己的配置文件中的配置 /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/conf/mongodb.conf\n\t2. 也可以输入配置目录启动 /usr/local/mongodb/bin/mongod --dbpath=/usr/local/mongodb/data/ --port=12345 --fork --logpath=/usr/local/mongodb/log/mongodb.log\n8. 启动之后 输入 > mongo 127.0.0.1:12345/admin 连接mongodb服务\n\t目前没有设置用户名密码 所以需要无认证启动, so先设置用户名密码\n9. mongodb中用户是归属于数据库的 ,可以说是为数据库设置自己的用户,并设置权限,一般一个用户只是管理一个数据库\n\t(当然,可以设置一个超级管理员用来管理所有的数据库)\n\t*下面的意思是为admin数据库设置一个用户名为\"root\",密码为\"root\"的用户,用户权限(角色) 是root(超级管理员)*\n\n\n > use admin\n > db.createUser(\n ... {\n ... user: \"root\",\n ... pwd: \"root\",\n ... roles: [ { role: \"root\", db: \"admin\" } ]\n ... }\n ... )\n \n \n*上面是mongodb3.0的新建用户方式, 2.x的方式有所不同,自行查阅*\n\n下面是mongodb内置的角色\n\n\n 1. 数据库用户角色:read、readWrite;\n 2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;\n 3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;\n 4. 备份恢复角色:backup、restore;\n 5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase\n 6. 超级用户角色:root \n // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)\n 7. 内部角色:__system\n\t\n\t\nRead:允许用户读取指定数据库\nreadWrite:允许用户读写指定数据库\ndbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile\nuserAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户\nclusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。\nreadAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限\nreadWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限\nuserAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限\ndbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。\nroot:只在admin数据库中可用。超级账号,超级权限\n\n\n\n 也可以创建角色:\n\n \n > db.createRole(\n ... {\n ... role: \"manageOpRole\",\n ... privileges: [ \n ... { resource: { cluster: true }, actions: [ \"killop\", \"inprog\" ] }, \n ... { resource: { db: \"my_test\", collection: \"my_collection\" }, \n ... actions: [ \"insert\", \"update\", \"remove\", \"compact\"] } \n ... ],\n ... roles: [] \n ... }\n ... )\n \n {\n \"role\" : \"manageOpRole\",\n \"privileges\" : [\n {\n \"resource\" : {\n \"cluster\" : true\n },\n \"actions\" : [\n \"killop\",\n \"inprog\"\n ]\n },\n {\n \"resource\" : {\n \"db\" : \"my_test\",\n \"collection\" : \"my_collection\"\n },\n \"actions\" : [\n \"insert\", \n \"update\", \n \"remove\", \n \"compact\"\n ]\n }\n ],\n \"roles\" : [ ]\n }\n > \n\n\n*可以添加几个其他角色的用户来测试权限*\n\n10. 要用用户登录的服务 so先关闭服务: > db.shutdownServer()\n\n11. 启动带权限验证的mongodb服务: \n\n\n > /usr/local/mongodb/bin/mongod --dbpath=/usr/local/mongodb/data/ --port=12345 --fork --logpath=/usr/local/mongodb/log/mongodb.log -auth\n \n \n\t如果报错too many positional options是由于--的原因,需要写英文的两个-\n12. 连接 > mongo 127.0.0.1:12345/admin\n13. 使用数据库 use admin\n14. 进行一些数据库操作 比如 > show dbs 此时就会报错 用用户名密码验证权限\n\n \n > db.auth('root','anyao112233')\n \n \n\t返回1表示成功 返回0表示失败 ; 此时再输入:\n\t\n> show dbs\n> show collections\n\n\n\t就会返回正常结果;\n\n\n# 常见问题解决\n##### *注意* : > use test //用来切换别的数据库\n##### 此时如果登录的用户没有操作此数据库的权限 show dbs就会报错\n\n\t#####*注意* : 此时关闭服务> db.shutdownServer() 可能会报错,它提示没有shutdown的权限\n\t\t解决方法: > db.grantRolesToUser( \"root\" , [ { role: \"hostManager\", db: \"admin\" } ])\n\n\t为用户root赋予hostManager角色的权限,然后就可以关闭了\n\t\n15. 输入> exit 退出界面\n\n\t*注*:可以用浏览器访问 127.0.0.1:27017\n\t*注*:有时候shutdown以后 无法再启动 报错\n\t\t原因:1. mongodb没有正常关闭 \n\t\t\t\t解决方法:删除mongodb的data目录下的mongod.lock (不能解决就把log目录中日志删除)\n\t\t\t 2.上面试了还是无法启动,那就是mongodb服务可能没有访问data,log等目录的权限\n\t\t\t \t解决方法:> chmod -R 777 /usr/local/mongodb/\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","slug":"2017_Mongodb","published":1,"updated":"2018-01-13T02:29:22.227Z","layout":"post","photos":[],"link":"","_id":"ckm3inuvy002a24ujbms1drmt","content":"

MongoDB 是一个基于分布式文件存储的数据库。由C++语言编写;是一个介于关系数据库和非关系数据库之间的产品

其支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

使用原理 :

\n

面向集合: 数据被分组存储在数据集中,被称为一个集合(Collection),每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档 (类似于传统数据库中的 表(table))

\n
\n
\n

模式自由: 意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义;存储在集合中的文档,

\n
\n
\n

键-值对的形式:键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized Document Format)

\n
\n\n

安装

    \n
  1. 环境:CentOs6.5 64位 远程工具 xsell
  2. \n
  3. 下载:官网下载 https://www.mongodb.com/download-center?jmp=nav#community 或者
    输入命令: > curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz

    \n
  4. \n
  5. 解压: > tar -zxvf mongodb-linux-x86_64-3.0.6.tgz

    \n
  6. \n
  7. 移动到目录 mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb
  8. \n
  9. 把bin目录添加到环境变量PATH中 : vim /etc/profile
    添加或修改为: export PATH=”/usr/local/mongodb/bin:$PATH”
  10. \n
  11. 配置自己的数据,日志等目录
    I. > cd /usr/local/mongodb
    II. > mkdir data
    III. > mkdir log
    IV. > mkdir conf
    V. > cd conf
    VI. > touch mongodb.conf
    VII. > vim mongodb.conf
  12. \n
\n
port = 27017\ndbpath = data\nlogpath = log/mongod.log\nfork = true\n

启动

    \n
  1. 配置完毕 启动服务
      \n
    1. 可以使用自己的配置文件中的配置 /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/conf/mongodb.conf
    2. \n
    3. 也可以输入配置目录启动 /usr/local/mongodb/bin/mongod –dbpath=/usr/local/mongodb/data/ –port=12345 –fork –logpath=/usr/local/mongodb/log/mongodb.log
    4. \n
    \n
  2. \n
  3. 启动之后 输入 > mongo 127.0.0.1:12345/admin 连接mongodb服务
    目前没有设置用户名密码 所以需要无认证启动, so先设置用户名密码
  4. \n
  5. mongodb中用户是归属于数据库的 ,可以说是为数据库设置自己的用户,并设置权限,一般一个用户只是管理一个数据库
    (当然,可以设置一个超级管理员用来管理所有的数据库)
    下面的意思是为admin数据库设置一个用户名为”root”,密码为”root”的用户,用户权限(角色) 是root(超级管理员)
  6. \n
\n
> use admin\n> db.createUser(\n...   {\n...     user: "root",\n...     pwd: "root",\n...     roles: [ { role: "root", db: "admin" } ]\n...   }\n... )\n

上面是mongodb3.0的新建用户方式, 2.x的方式有所不同,自行查阅

\n

下面是mongodb内置的角色

\n
1. 数据库用户角色:read、readWrite;\n2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;\n3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;\n4. 备份恢复角色:backup、restore;\n5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase\n6. 超级用户角色:root  \n// 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)\n7. 内部角色:__system\n

Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限

\n
也可以创建角色:\n\n\n> db.createRole(\n... {\n...     role: "manageOpRole",\n...     privileges: [ \n...         { resource: { cluster: true }, actions: [ "killop", "inprog" ] }, \n...         { resource: { db: "my_test", collection: "my_collection" }, \n...            actions: [ "insert", "update", "remove", "compact"] } \n...                 ],\n...     roles: [] \n...     }\n... )\n\n{\n    "role" : "manageOpRole",\n    "privileges" : [\n        {\n            "resource" : {\n                "cluster" : true\n            },\n            "actions" : [\n                "killop",\n                "inprog"\n            ]\n        },\n        {\n            "resource" : {\n                "db" : "my_test",\n                "collection" : "my_collection"\n            },\n            "actions" : [\n                "insert", \n                "update", \n                "remove", \n                "compact"\n            ]\n        }\n    ],\n    "roles" : [ ]\n}\n> \n

可以添加几个其他角色的用户来测试权限

\n
    \n
  1. 要用用户登录的服务 so先关闭服务: > db.shutdownServer()

    \n
  2. \n
  3. 启动带权限验证的mongodb服务:

    \n
  4. \n
\n
    > /usr/local/mongodb/bin/mongod --dbpath=/usr/local/mongodb/data/  --port=12345  --fork --logpath=/usr/local/mongodb/log/mongodb.log -auth\n\n\n如果报错too many positional options是由于--的原因,需要写英文的两个-\n
    \n
  1. 连接 > mongo 127.0.0.1:12345/admin
  2. \n
  3. 使用数据库 use admin
  4. \n
  5. 进行一些数据库操作 比如 > show dbs 此时就会报错 用用户名密码验证权限
  6. \n
\n
    > db.auth('root','anyao112233')\n\n\n返回1表示成功 返回0表示失败 ; 此时再输入:\n
\n

show dbs
show collections

\n
\n
就会返回正常结果;\n

常见问题解决

注意 : > use test //用来切换别的数据库
此时如果登录的用户没有操作此数据库的权限 show dbs就会报错
#####*注意* : 此时关闭服务>  db.shutdownServer() 可能会报错,它提示没有shutdown的权限\n    解决方法: > db.grantRolesToUser( "root" , [ { role: "hostManager", db: "admin" } ])\n\n为用户root赋予hostManager角色的权限,然后就可以关闭了\n
    \n
  1. 输入> exit 退出界面

    \n

    :可以用浏览器访问 127.0.0.1:27017
    :有时候shutdown以后 无法再启动 报错

    \n
    原因:1. mongodb没有正常关闭 \n        解决方法:删除mongodb的data目录下的mongod.lock (不能解决就把log目录中日志删除)\n     2.上面试了还是无法启动,那就是mongodb服务可能没有访问data,log等目录的权限\n         解决方法:> chmod -R 777 /usr/local/mongodb/\n
  2. \n
\n","site":{"data":{}},"excerpt":"

MongoDB 是一个基于分布式文件存储的数据库。由C++语言编写;是一个介于关系数据库和非关系数据库之间的产品

其支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

使用原理 :

\n

面向集合: 数据被分组存储在数据集中,被称为一个集合(Collection),每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档 (类似于传统数据库中的 表(table))

\n
\n
\n

模式自由: 意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义;存储在集合中的文档,

\n
\n
\n

键-值对的形式:键用于唯一标识一个文档,为字符串类型,而值则可以是各种复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized Document Format)

\n
","more":"

安装

    \n
  1. 环境:CentOs6.5 64位 远程工具 xsell
  2. \n
  3. 下载:官网下载 https://www.mongodb.com/download-center?jmp=nav#community 或者
    输入命令: > curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz

    \n
  4. \n
  5. 解压: > tar -zxvf mongodb-linux-x86_64-3.0.6.tgz

    \n
  6. \n
  7. 移动到目录 mv mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb
  8. \n
  9. 把bin目录添加到环境变量PATH中 : vim /etc/profile
    添加或修改为: export PATH=”/usr/local/mongodb/bin:$PATH”
  10. \n
  11. 配置自己的数据,日志等目录
    I. > cd /usr/local/mongodb
    II. > mkdir data
    III. > mkdir log
    IV. > mkdir conf
    V. > cd conf
    VI. > touch mongodb.conf
    VII. > vim mongodb.conf
  12. \n
\n
port = 27017\ndbpath = data\nlogpath = log/mongod.log\nfork = true\n

启动

    \n
  1. 配置完毕 启动服务
      \n
    1. 可以使用自己的配置文件中的配置 /usr/local/mongodb/bin/mongod -f /usr/local/mongodb/conf/mongodb.conf
    2. \n
    3. 也可以输入配置目录启动 /usr/local/mongodb/bin/mongod –dbpath=/usr/local/mongodb/data/ –port=12345 –fork –logpath=/usr/local/mongodb/log/mongodb.log
    4. \n
    \n
  2. \n
  3. 启动之后 输入 > mongo 127.0.0.1:12345/admin 连接mongodb服务
    目前没有设置用户名密码 所以需要无认证启动, so先设置用户名密码
  4. \n
  5. mongodb中用户是归属于数据库的 ,可以说是为数据库设置自己的用户,并设置权限,一般一个用户只是管理一个数据库
    (当然,可以设置一个超级管理员用来管理所有的数据库)
    下面的意思是为admin数据库设置一个用户名为”root”,密码为”root”的用户,用户权限(角色) 是root(超级管理员)
  6. \n
\n
> use admin\n> db.createUser(\n...   {\n...     user: "root",\n...     pwd: "root",\n...     roles: [ { role: "root", db: "admin" } ]\n...   }\n... )\n

上面是mongodb3.0的新建用户方式, 2.x的方式有所不同,自行查阅

\n

下面是mongodb内置的角色

\n
1. 数据库用户角色:read、readWrite;\n2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;\n3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;\n4. 备份恢复角色:backup、restore;\n5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase\n6. 超级用户角色:root  \n// 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)\n7. 内部角色:__system\n

Read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root:只在admin数据库中可用。超级账号,超级权限

\n
也可以创建角色:\n\n\n> db.createRole(\n... {\n...     role: "manageOpRole",\n...     privileges: [ \n...         { resource: { cluster: true }, actions: [ "killop", "inprog" ] }, \n...         { resource: { db: "my_test", collection: "my_collection" }, \n...            actions: [ "insert", "update", "remove", "compact"] } \n...                 ],\n...     roles: [] \n...     }\n... )\n\n{\n    "role" : "manageOpRole",\n    "privileges" : [\n        {\n            "resource" : {\n                "cluster" : true\n            },\n            "actions" : [\n                "killop",\n                "inprog"\n            ]\n        },\n        {\n            "resource" : {\n                "db" : "my_test",\n                "collection" : "my_collection"\n            },\n            "actions" : [\n                "insert", \n                "update", \n                "remove", \n                "compact"\n            ]\n        }\n    ],\n    "roles" : [ ]\n}\n> \n

可以添加几个其他角色的用户来测试权限

\n
    \n
  1. 要用用户登录的服务 so先关闭服务: > db.shutdownServer()

    \n
  2. \n
  3. 启动带权限验证的mongodb服务:

    \n
  4. \n
\n
    > /usr/local/mongodb/bin/mongod --dbpath=/usr/local/mongodb/data/  --port=12345  --fork --logpath=/usr/local/mongodb/log/mongodb.log -auth\n\n\n如果报错too many positional options是由于--的原因,需要写英文的两个-\n
    \n
  1. 连接 > mongo 127.0.0.1:12345/admin
  2. \n
  3. 使用数据库 use admin
  4. \n
  5. 进行一些数据库操作 比如 > show dbs 此时就会报错 用用户名密码验证权限
  6. \n
\n
    > db.auth('root','anyao112233')\n\n\n返回1表示成功 返回0表示失败 ; 此时再输入:\n
\n

show dbs
show collections

\n
\n
就会返回正常结果;\n

常见问题解决

注意 : > use test //用来切换别的数据库
此时如果登录的用户没有操作此数据库的权限 show dbs就会报错
#####*注意* : 此时关闭服务>  db.shutdownServer() 可能会报错,它提示没有shutdown的权限\n    解决方法: > db.grantRolesToUser( "root" , [ { role: "hostManager", db: "admin" } ])\n\n为用户root赋予hostManager角色的权限,然后就可以关闭了\n
    \n
  1. 输入> exit 退出界面

    \n

    :可以用浏览器访问 127.0.0.1:27017
    :有时候shutdown以后 无法再启动 报错

    \n
    原因:1. mongodb没有正常关闭 \n        解决方法:删除mongodb的data目录下的mongod.lock (不能解决就把log目录中日志删除)\n     2.上面试了还是无法启动,那就是mongodb服务可能没有访问data,log等目录的权限\n         解决方法:> chmod -R 777 /usr/local/mongodb/\n
  2. \n
"},{"title":"运用存储过程批量更新数据库中某个字段","comments":1,"description":null,"date":"2017-02-04T16:00:00.000Z","_content":"\n**这是目标表 **\n![](http://115.159.40.33/wp-content/uploads/2017/02/1.png)\n**这是来源表 **\n![](http://115.159.40.33/wp-content/uploads/2017/02/2.png)\n**目的:要把exam_add表中的memo字段根据idCard字段对应更新到w_secondary_score表的memo\n\n**\n\n\n\n```sql\nBEGIN\n DECLARE pidCard varchar(20); /*存放idCard*/\nDECLARE pmemo varchar(255); /*存放memo*/\n\n declare done int default -1; \n DECLARE cur CURSOR FOR(SELECT idCard, memo from exam_end); /*定义一个游标*/\n DECLARE continue handler for not found set done=1; \n\n OPEN cur;\n myLoop: LOOP \n FETCH cur INTO pidCard,pmemo; /*把游标内数据赋值给变量*/\n\n if done = 1 then \n leave myLoop; \n end if; \n\n UPDATE w_secondary_score set memo = pmemo where idCard = pidCard; /*循环更新*/\n\n end loop myLoop; \n CLOSE cur;\n```\n\n**更新成功!!!!!!**\n**ps:在导入excel表到数据库的时候(通过navicat软件),出现中文乱码, 解决方案: **\n\n**方法1.**把excel表格编码修改为与数据库相同的编码(我的是utf-8),像这样 \n![](http://115.159.40.33/wp-content/uploads/2017/02/3.png)\n(百度说可以, 然而我试了依然乱码) \n\n**方法2**:我看到navicat可以导入.txt文件 那么可以把excel先转为.txt文件设置编码为utf-8,然后再导入—–>成功!\n![](http://115.159.40.33/wp-content/uploads/2017/02/4.png)\n![](http://115.159.40.33/wp-content/uploads/2017/02/5.png)\n![](http://115.159.40.33/wp-content/uploads/2017/02/6.png)","source":"_posts/2017_Procedure.md","raw":"---\ntitle: 运用存储过程批量更新数据库中某个字段\n\ncomments: true \n\ntags: \n - mysql\n - 存储过程\n\ncategories: \n - 数据库\n\ndescription: \n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n**这是目标表 **\n![](http://115.159.40.33/wp-content/uploads/2017/02/1.png)\n**这是来源表 **\n![](http://115.159.40.33/wp-content/uploads/2017/02/2.png)\n**目的:要把exam_add表中的memo字段根据idCard字段对应更新到w_secondary_score表的memo\n\n**\n\n\n\n```sql\nBEGIN\n DECLARE pidCard varchar(20); /*存放idCard*/\nDECLARE pmemo varchar(255); /*存放memo*/\n\n declare done int default -1; \n DECLARE cur CURSOR FOR(SELECT idCard, memo from exam_end); /*定义一个游标*/\n DECLARE continue handler for not found set done=1; \n\n OPEN cur;\n myLoop: LOOP \n FETCH cur INTO pidCard,pmemo; /*把游标内数据赋值给变量*/\n\n if done = 1 then \n leave myLoop; \n end if; \n\n UPDATE w_secondary_score set memo = pmemo where idCard = pidCard; /*循环更新*/\n\n end loop myLoop; \n CLOSE cur;\n```\n\n**更新成功!!!!!!**\n**ps:在导入excel表到数据库的时候(通过navicat软件),出现中文乱码, 解决方案: **\n\n**方法1.**把excel表格编码修改为与数据库相同的编码(我的是utf-8),像这样 \n![](http://115.159.40.33/wp-content/uploads/2017/02/3.png)\n(百度说可以, 然而我试了依然乱码) \n\n**方法2**:我看到navicat可以导入.txt文件 那么可以把excel先转为.txt文件设置编码为utf-8,然后再导入—–>成功!\n![](http://115.159.40.33/wp-content/uploads/2017/02/4.png)\n![](http://115.159.40.33/wp-content/uploads/2017/02/5.png)\n![](http://115.159.40.33/wp-content/uploads/2017/02/6.png)","slug":"2017_Procedure","published":1,"updated":"2018-01-13T02:29:22.231Z","layout":"post","photos":[],"link":"","_id":"ckm3inuw0002e24uj47b0m1jw","content":"

这是目标表

这是来源表

**目的:要把exam_add表中的memo字段根据idCard字段对应更新到w_secondary_score表的memo

\n

**

\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BEGIN
DECLARE pidCard varchar(20); /*存放idCard*/
DECLARE pmemo varchar(255); /*存放memo*/

declare done int default -1;
DECLARE cur CURSOR FOR(SELECT idCard, memo from exam_end); /*定义一个游标*/
DECLARE continue handler for not found set done=1;

OPEN cur;
myLoop: LOOP
FETCH cur INTO pidCard,pmemo; /*把游标内数据赋值给变量*/

if done = 1 then
leave myLoop;
end if;

UPDATE w_secondary_score set memo = pmemo where idCard = pidCard; /*循环更新*/

end loop myLoop;
CLOSE cur;
\n

更新成功!!!!!!
ps:在导入excel表到数据库的时候(通过navicat软件),出现中文乱码, 解决方案:

\n

方法1.把excel表格编码修改为与数据库相同的编码(我的是utf-8),像这样

(百度说可以, 然而我试了依然乱码)

\n

方法2:我看到navicat可以导入.txt文件 那么可以把excel先转为.txt文件设置编码为utf-8,然后再导入—–>成功!


\n","site":{"data":{}},"excerpt":"

这是目标表

这是来源表

**目的:要把exam_add表中的memo字段根据idCard字段对应更新到w_secondary_score表的memo

\n

**

","more":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BEGIN
DECLARE pidCard varchar(20); /*存放idCard*/
DECLARE pmemo varchar(255); /*存放memo*/

declare done int default -1;
DECLARE cur CURSOR FOR(SELECT idCard, memo from exam_end); /*定义一个游标*/
DECLARE continue handler for not found set done=1;

OPEN cur;
myLoop: LOOP
FETCH cur INTO pidCard,pmemo; /*把游标内数据赋值给变量*/

if done = 1 then
leave myLoop;
end if;

UPDATE w_secondary_score set memo = pmemo where idCard = pidCard; /*循环更新*/

end loop myLoop;
CLOSE cur;
\n

更新成功!!!!!!
ps:在导入excel表到数据库的时候(通过navicat软件),出现中文乱码, 解决方案:

\n

方法1.把excel表格编码修改为与数据库相同的编码(我的是utf-8),像这样

(百度说可以, 然而我试了依然乱码)

\n

方法2:我看到navicat可以导入.txt文件 那么可以把excel先转为.txt文件设置编码为utf-8,然后再导入—–>成功!


"},{"title":"XPath解析xml文档","comments":1,"description":"XPath解析xml文档","date":"2017-02-04T16:00:00.000Z","_content":"\n\n\n**这是被解析的xml文档示例**\n```xml\n \n <用户>\n \n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n \n```\n------------------------------------------------------------------------------\n\n```java\n package cn.xml;\n \n import java.io.File;\n \n import org.dom4j.Document;\n import org.dom4j.DocumentException;\n import org.dom4j.Node;\n import org.dom4j.io.SAXReader;\n import org.junit.Test;\n \n //用XPath提取xml文档数据\n public class Xpath{\n @Test\n public void read() throws DocumentException {\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n String s = document.selectSingleNode(\"//作者\").getText();//得到第一个作者的内容\n //selectSingleNode是取第一个\"作者\"节点 要取所有则用selectNodes\n System.out.println(s);\n }\n \n @Test\n public void find() throws DocumentException {//检测xml文档中有没有相匹配的用户账号密码\n String username = \"aaa\";\n String password = \"123\";\n \n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n //选择含有属性username且其值为'aa'的user元素( 这里注意空格有无是不同的)\n Node node = document.selectSingleNode(\"//user[@username= '\" + username + \"' and @password= '\" + password + \"' ]\");\n if (node == null) {\n System.out.println(\"用户名/密码错误\");\n }else{\n System.out.println(\"登陆成功\");\n }\n }\n }\n```","source":"_posts/2017_XPath.md","raw":"---\ntitle: XPath解析xml文档\n\ncomments: true \n\ntags: XPath\n\ncategories: \n - DOM操作 \n - XML\n\ndescription: XPath解析xml文档\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n\n\n**这是被解析的xml文档示例**\n```xml\n \n <用户>\n \n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n \n```\n------------------------------------------------------------------------------\n\n```java\n package cn.xml;\n \n import java.io.File;\n \n import org.dom4j.Document;\n import org.dom4j.DocumentException;\n import org.dom4j.Node;\n import org.dom4j.io.SAXReader;\n import org.junit.Test;\n \n //用XPath提取xml文档数据\n public class Xpath{\n @Test\n public void read() throws DocumentException {\n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n String s = document.selectSingleNode(\"//作者\").getText();//得到第一个作者的内容\n //selectSingleNode是取第一个\"作者\"节点 要取所有则用selectNodes\n System.out.println(s);\n }\n \n @Test\n public void find() throws DocumentException {//检测xml文档中有没有相匹配的用户账号密码\n String username = \"aaa\";\n String password = \"123\";\n \n SAXReader reader = new SAXReader();//解析器\n Document document = reader.read(new File(\"src/book.xml\"));//解析\n \n //选择含有属性username且其值为'aa'的user元素( 这里注意空格有无是不同的)\n Node node = document.selectSingleNode(\"//user[@username= '\" + username + \"' and @password= '\" + password + \"' ]\");\n if (node == null) {\n System.out.println(\"用户名/密码错误\");\n }else{\n System.out.println(\"登陆成功\");\n }\n }\n }\n```","slug":"2017_XPath","published":1,"updated":"2018-01-13T02:29:22.235Z","layout":"post","photos":[],"link":"","_id":"ckm3inuw2002i24ujkrhublry","content":"\n

这是被解析的xml文档示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<用户>
<user id=\"1\" username=\"aaa\" password=\"123\" email=\"abc.com\"/>
<user id=\"2\" username=\"bbb\" password=\"123\" email=\"abc.com\"/>
<书架>
<>
<书名>java实战</书名>
<作者>张三</作者>
<售价>121元</售价>
<售价>12元</售价>
</>
<>
<书名 color=\"yellow\" name=\"XXX\">c测试</书名>
<作者>李四</作者>
<售价 color=\"rrr\">54元</售价>
<售价>12元</售价></>
</书架>
</用户>

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.xml;

import java.io.File;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.junit.Test;

//用XPath提取xml文档数据
public class Xpath{
@Test
public void read() throws DocumentException {
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

String s = document.selectSingleNode(\"//作者\").getText();//得到第一个作者的内容
//selectSingleNode是取第一个\"作者\"节点 要取所有则用selectNodes
System.out.println(s);
}

@Test
public void find() throws DocumentException {//检测xml文档中有没有相匹配的用户账号密码
String username = \"aaa\";
String password = \"123\";

SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

//选择含有属性username且其值为'aa'的user元素( 这里注意空格有无是不同的)
Node node = document.selectSingleNode(\"//user[@username= '\" + username + \"' and @password= '\" + password + \"' ]\");
if (node == null) {
System.out.println(\"用户名/密码错误\");
}else{
System.out.println(\"登陆成功\");
}
}
}
","site":{"data":{}},"excerpt":"","more":"

这是被解析的xml文档示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<用户>
<user id=\"1\" username=\"aaa\" password=\"123\" email=\"abc.com\"/>
<user id=\"2\" username=\"bbb\" password=\"123\" email=\"abc.com\"/>
<书架>
<>
<书名>java实战</书名>
<作者>张三</作者>
<售价>121元</售价>
<售价>12元</售价>
</>
<>
<书名 color=\"yellow\" name=\"XXX\">c测试</书名>
<作者>李四</作者>
<售价 color=\"rrr\">54元</售价>
<售价>12元</售价></>
</书架>
</用户>

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.xml;

import java.io.File;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.junit.Test;

//用XPath提取xml文档数据
public class Xpath{
@Test
public void read() throws DocumentException {
SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

String s = document.selectSingleNode(\"//作者\").getText();//得到第一个作者的内容
//selectSingleNode是取第一个\"作者\"节点 要取所有则用selectNodes
System.out.println(s);
}

@Test
public void find() throws DocumentException {//检测xml文档中有没有相匹配的用户账号密码
String username = \"aaa\";
String password = \"123\";

SAXReader reader = new SAXReader();//解析器
Document document = reader.read(new File(\"src/book.xml\"));//解析

//选择含有属性username且其值为'aa'的user元素( 这里注意空格有无是不同的)
Node node = document.selectSingleNode(\"//user[@username= '\" + username + \"' and @password= '\" + password + \"' ]\");
if (node == null) {
System.out.println(\"用户名/密码错误\");
}else{
System.out.println(\"登陆成功\");
}
}
}
"},{"title":"Centos下用yum安装mysql5.6","comments":1,"description":"转载的,最简单的centos装mysql的教程","date":"2017-02-04T16:00:00.000Z","_content":"\n 1. 输入命令 查看当前安装的mysql: rpm -qa | grep mysql\n 2. 如果有 卸载: rpm -e mysql  // 普通删除模式\n rpm -e --nodeps mysql  // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除\n \n 3. 查看yum上提供的mysql版本信息 : yum list | grep mysql\n \n 4.安装 : yum install -y mysql-server mysql mysql-devel\n\n \n 在CentOS7下安装mysql 可能会提示“No package mysql-server available.”\n \n 解决办法: rpm -Uvh http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm\n 然后再安装 mysql-server\n \n \n 5.查看安装好的mysql信息: rpm -qi mysql-server\n \n \n 6.启动: service mysqld start\n \n \n 7. 设置密码: mysql -u root 进入mysql界面\n SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');\n \n 8. 新建新用户,用于远程连接(也可以修改root用户访问权限)\n CREATE USER 'username'@'host' IDENTIFIED BY 'password'; \n \n * username - 你将创建的用户名, \n * host - 指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost, 如果想让该用户可以从任意远程主机登陆,可以使用通配符%. \n * password - 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器\n \n \n 9. 为用户授权: GRANT privileges ON databasename.tablename TO 'username'@'host' \n \n * privileges - 用户的操作权限,如SELECT , INSERT , UPDATE 等(详细列表见该文最后面).如果要授予所的权限则使用ALL.;\n * databasename - 数据库名,\n * tablename-表名,如果要授予该用户对所有数据库和表的相应操作权限则可用*表示, 如*.*. \n\n 10.设置密码: SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');","source":"_posts/2017_centos.md","raw":"---\ntitle: Centos下用yum安装mysql5.6\n\ncomments: true \n\ntags: mysql\n\ncategories: \n - 数据库\n\ndescription: 转载的,最简单的centos装mysql的教程\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n 1. 输入命令 查看当前安装的mysql: rpm -qa | grep mysql\n 2. 如果有 卸载: rpm -e mysql  // 普通删除模式\n rpm -e --nodeps mysql  // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除\n \n 3. 查看yum上提供的mysql版本信息 : yum list | grep mysql\n \n 4.安装 : yum install -y mysql-server mysql mysql-devel\n\n \n 在CentOS7下安装mysql 可能会提示“No package mysql-server available.”\n \n 解决办法: rpm -Uvh http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm\n 然后再安装 mysql-server\n \n \n 5.查看安装好的mysql信息: rpm -qi mysql-server\n \n \n 6.启动: service mysqld start\n \n \n 7. 设置密码: mysql -u root 进入mysql界面\n SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');\n \n 8. 新建新用户,用于远程连接(也可以修改root用户访问权限)\n CREATE USER 'username'@'host' IDENTIFIED BY 'password'; \n \n * username - 你将创建的用户名, \n * host - 指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost, 如果想让该用户可以从任意远程主机登陆,可以使用通配符%. \n * password - 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器\n \n \n 9. 为用户授权: GRANT privileges ON databasename.tablename TO 'username'@'host' \n \n * privileges - 用户的操作权限,如SELECT , INSERT , UPDATE 等(详细列表见该文最后面).如果要授予所的权限则使用ALL.;\n * databasename - 数据库名,\n * tablename-表名,如果要授予该用户对所有数据库和表的相应操作权限则可用*表示, 如*.*. \n\n 10.设置密码: SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');","slug":"2017_centos","published":1,"updated":"2018-01-13T02:29:22.238Z","layout":"post","photos":[],"link":"","_id":"ckm3inuw4002m24ujq3ypkhmt","content":"
1. 输入命令 查看当前安装的mysql:  rpm -qa | grep mysql\n2. 如果有 卸载:    rpm -e mysql  // 普通删除模式\n                  rpm -e --nodeps mysql  // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除\n\n3. 查看yum上提供的mysql版本信息 : yum list | grep mysql\n\n4.安装 :    yum install -y mysql-server mysql mysql-devel\n\n\n    在CentOS7下安装mysql 可能会提示“No package mysql-server available.”\n\n    解决办法: rpm -Uvh http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm\n            然后再安装 mysql-server\n\n\n5.查看安装好的mysql信息: rpm -qi mysql-server\n\n\n6.启动:  service mysqld start\n\n\n7. 设置密码: mysql -u root 进入mysql界面\n            SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');\n\n8. 新建新用户,用于远程连接(也可以修改root用户访问权限)\n            CREATE USER 'username'@'host' IDENTIFIED BY 'password'; \n\n            * username - 你将创建的用户名, \n            * host - 指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost, 如果想让该用户可以从任意远程主机登陆,可以使用通配符%. \n            * password - 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器\n\n\n9. 为用户授权: GRANT privileges ON databasename.tablename TO 'username'@'host' \n\n            * privileges - 用户的操作权限,如SELECT , INSERT , UPDATE 等(详细列表见该文最后面).如果要授予所的权限则使用ALL.;\n            * databasename - 数据库名,\n            * tablename-表名,如果要授予该用户对所有数据库和表的相应操作权限则可用*表示, 如*.*. \n\n10.设置密码: SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');\n
","site":{"data":{}},"excerpt":"","more":"
1. 输入命令 查看当前安装的mysql:  rpm -qa | grep mysql\n2. 如果有 卸载:    rpm -e mysql  // 普通删除模式\n                  rpm -e --nodeps mysql  // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除\n\n3. 查看yum上提供的mysql版本信息 : yum list | grep mysql\n\n4.安装 :    yum install -y mysql-server mysql mysql-devel\n\n\n    在CentOS7下安装mysql 可能会提示“No package mysql-server available.”\n\n    解决办法: rpm -Uvh http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm\n            然后再安装 mysql-server\n\n\n5.查看安装好的mysql信息: rpm -qi mysql-server\n\n\n6.启动:  service mysqld start\n\n\n7. 设置密码: mysql -u root 进入mysql界面\n            SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass');\n\n8. 新建新用户,用于远程连接(也可以修改root用户访问权限)\n            CREATE USER 'username'@'host' IDENTIFIED BY 'password'; \n\n            * username - 你将创建的用户名, \n            * host - 指定该用户在哪个主机上可以登陆,如果是本地用户可用localhost, 如果想让该用户可以从任意远程主机登陆,可以使用通配符%. \n            * password - 该用户的登陆密码,密码可以为空,如果为空则该用户可以不需要密码登陆服务器\n\n\n9. 为用户授权: GRANT privileges ON databasename.tablename TO 'username'@'host' \n\n            * privileges - 用户的操作权限,如SELECT , INSERT , UPDATE 等(详细列表见该文最后面).如果要授予所的权限则使用ALL.;\n            * databasename - 数据库名,\n            * tablename-表名,如果要授予该用户对所有数据库和表的相应操作权限则可用*表示, 如*.*. \n\n10.设置密码: SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');\n
"},{"title":"c3p0参数解释(转载)","comments":1,"description":null,"date":"2017-02-13T16:00:00.000Z","_content":"\nhttp://blog.csdn.net/xb12369/article/details/41517409\n\n#最常用配置\n#initialPoolSize:连接池初始化时创建的连接数,default : 3,取值应在minPoolSize与maxPoolSize之间\nc3p0.initialPoolSize=10\n\n#minPoolSize:连接池保持的最小连接数,default : 3\nc3p0.minPoolSize=10\n\n#maxPoolSize:连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15\nc3p0.maxPoolSize=50\n\n#acquireIncrement:连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3\nc3p0.acquireIncrement=5\n\n\n\n\n#管理连接池的大小和连接的生存时间\n#maxIdleTime:连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。default : 0 单位 s\nc3p0.maxIdleTime=600\n\n#idleConnectionTestPeriod:每900秒检查所有连接池中的空闲连接\nc3p0.idleConnectionTestPeriod=900\n\n\n#配置PreparedStatement缓存\n#连接池为数据源缓存的PreparedStatement的总数。由于PreparedStatement属于单个Connection,所以这个数量应该根据应用中平均连接数乘以每个连接的平均PreparedStatement\n#来计算。同时maxStatementsPerConnection的配置无效。default : 0(不建议使用)\nc3p0.maxStatements=500\n\n#连接池为数据源单个Connection缓存的PreparedStatement数,这个配置比maxStatements更有意义,因为它缓存的服务对象是单个数据连接,\n#如果设置的好,肯定是可以提高性能的。为0的时候不缓存。default : 0(看情况而论)\nc3p0.maxStatementsPerConnection=30\n\n\n#重连相关配置 \n#acquireRetryAttempts:连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。default : 30(建议使用)\nc3p0.acquireRetryAttempts=5\n\n#acquireRetryDelay:两次连接中间隔时间,单位毫秒,连接池在获得新连接时的间隔时间。default : 1000 单位ms(建议使用)\nc3p0.acquireRetryDelay=1000\n\n#breakAfterAcquireFailure:如果为true,则当连接获取失败时自动关闭数据源,除非重新启动应用程序。所以一般不用。default : false(不建议使用)\nc3p0.breakAfterAcquireFailure=false\n\n#checkoutTimeout:配置当连接池所有连接用完时应用程序getConnection的等待时间。为0则无限等待直至有其他连接释放或者创建新的连接,\n#不为0则当时间到的时候如果仍没有获得连接,则会抛出SQLException。其实就是acquireRetryAttempts*acquireRetryDelay。default : 0(与上面两个,有重复,选择其中两个都行)\nc3p0.checkoutTimeout=100\n\n\n#其他\n#autoCommitOnClose:连接池在回收数据库连接时是否自动提交事务。如果为false,则会回滚未提交的事务,如果为true,则会自动提交事务。default : false(不建议使用)\nc3p0.autoCommitOnClose=false\n\n#c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3\nc3p0.numHelperThreads=10","source":"_posts/2017_c3p0.md","raw":"---\ntitle: c3p0参数解释(转载)\n\ncomments: true \n\ntags: \n - C3P0\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-14\n \n---\n\nhttp://blog.csdn.net/xb12369/article/details/41517409\n\n#最常用配置\n#initialPoolSize:连接池初始化时创建的连接数,default : 3,取值应在minPoolSize与maxPoolSize之间\nc3p0.initialPoolSize=10\n\n#minPoolSize:连接池保持的最小连接数,default : 3\nc3p0.minPoolSize=10\n\n#maxPoolSize:连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15\nc3p0.maxPoolSize=50\n\n#acquireIncrement:连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3\nc3p0.acquireIncrement=5\n\n\n\n\n#管理连接池的大小和连接的生存时间\n#maxIdleTime:连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。default : 0 单位 s\nc3p0.maxIdleTime=600\n\n#idleConnectionTestPeriod:每900秒检查所有连接池中的空闲连接\nc3p0.idleConnectionTestPeriod=900\n\n\n#配置PreparedStatement缓存\n#连接池为数据源缓存的PreparedStatement的总数。由于PreparedStatement属于单个Connection,所以这个数量应该根据应用中平均连接数乘以每个连接的平均PreparedStatement\n#来计算。同时maxStatementsPerConnection的配置无效。default : 0(不建议使用)\nc3p0.maxStatements=500\n\n#连接池为数据源单个Connection缓存的PreparedStatement数,这个配置比maxStatements更有意义,因为它缓存的服务对象是单个数据连接,\n#如果设置的好,肯定是可以提高性能的。为0的时候不缓存。default : 0(看情况而论)\nc3p0.maxStatementsPerConnection=30\n\n\n#重连相关配置 \n#acquireRetryAttempts:连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。default : 30(建议使用)\nc3p0.acquireRetryAttempts=5\n\n#acquireRetryDelay:两次连接中间隔时间,单位毫秒,连接池在获得新连接时的间隔时间。default : 1000 单位ms(建议使用)\nc3p0.acquireRetryDelay=1000\n\n#breakAfterAcquireFailure:如果为true,则当连接获取失败时自动关闭数据源,除非重新启动应用程序。所以一般不用。default : false(不建议使用)\nc3p0.breakAfterAcquireFailure=false\n\n#checkoutTimeout:配置当连接池所有连接用完时应用程序getConnection的等待时间。为0则无限等待直至有其他连接释放或者创建新的连接,\n#不为0则当时间到的时候如果仍没有获得连接,则会抛出SQLException。其实就是acquireRetryAttempts*acquireRetryDelay。default : 0(与上面两个,有重复,选择其中两个都行)\nc3p0.checkoutTimeout=100\n\n\n#其他\n#autoCommitOnClose:连接池在回收数据库连接时是否自动提交事务。如果为false,则会回滚未提交的事务,如果为true,则会自动提交事务。default : false(不建议使用)\nc3p0.autoCommitOnClose=false\n\n#c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3\nc3p0.numHelperThreads=10","slug":"2017_c3p0","published":1,"updated":"2018-01-13T02:29:22.237Z","layout":"post","photos":[],"link":"","_id":"ckm3inuw6002q24ujpqjknp2x","content":"

http://blog.csdn.net/xb12369/article/details/41517409

\n

#最常用配置

\n

#initialPoolSize:连接池初始化时创建的连接数,default : 3,取值应在minPoolSize与maxPoolSize之间
c3p0.initialPoolSize=10

\n

#minPoolSize:连接池保持的最小连接数,default : 3
c3p0.minPoolSize=10

\n

#maxPoolSize:连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15
c3p0.maxPoolSize=50

\n

#acquireIncrement:连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3
c3p0.acquireIncrement=5

\n\n

#管理连接池的大小和连接的生存时间

\n

#maxIdleTime:连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。default : 0 单位 s
c3p0.maxIdleTime=600

\n

#idleConnectionTestPeriod:每900秒检查所有连接池中的空闲连接
c3p0.idleConnectionTestPeriod=900

\n

#配置PreparedStatement缓存

\n

#连接池为数据源缓存的PreparedStatement的总数。由于PreparedStatement属于单个Connection,所以这个数量应该根据应用中平均连接数乘以每个连接的平均PreparedStatement

\n

#来计算。同时maxStatementsPerConnection的配置无效。default : 0(不建议使用)
c3p0.maxStatements=500

\n

#连接池为数据源单个Connection缓存的PreparedStatement数,这个配置比maxStatements更有意义,因为它缓存的服务对象是单个数据连接,

\n

#如果设置的好,肯定是可以提高性能的。为0的时候不缓存。default : 0(看情况而论)
c3p0.maxStatementsPerConnection=30

\n

#重连相关配置

\n

#acquireRetryAttempts:连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。default : 30(建议使用)
c3p0.acquireRetryAttempts=5

\n

#acquireRetryDelay:两次连接中间隔时间,单位毫秒,连接池在获得新连接时的间隔时间。default : 1000 单位ms(建议使用)
c3p0.acquireRetryDelay=1000

\n

#breakAfterAcquireFailure:如果为true,则当连接获取失败时自动关闭数据源,除非重新启动应用程序。所以一般不用。default : false(不建议使用)
c3p0.breakAfterAcquireFailure=false

\n

#checkoutTimeout:配置当连接池所有连接用完时应用程序getConnection的等待时间。为0则无限等待直至有其他连接释放或者创建新的连接,

\n

#不为0则当时间到的时候如果仍没有获得连接,则会抛出SQLException。其实就是acquireRetryAttempts*acquireRetryDelay。default : 0(与上面两个,有重复,选择其中两个都行)
c3p0.checkoutTimeout=100

\n

#其他

\n

#autoCommitOnClose:连接池在回收数据库连接时是否自动提交事务。如果为false,则会回滚未提交的事务,如果为true,则会自动提交事务。default : false(不建议使用)
c3p0.autoCommitOnClose=false

\n

#c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3
c3p0.numHelperThreads=10

\n","site":{"data":{}},"excerpt":"

http://blog.csdn.net/xb12369/article/details/41517409

\n

#最常用配置

\n

#initialPoolSize:连接池初始化时创建的连接数,default : 3,取值应在minPoolSize与maxPoolSize之间
c3p0.initialPoolSize=10

\n

#minPoolSize:连接池保持的最小连接数,default : 3
c3p0.minPoolSize=10

\n

#maxPoolSize:连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放,所以这个值有可能会设计地很大,default : 15
c3p0.maxPoolSize=50

\n

#acquireIncrement:连接池在无空闲连接可用时一次性创建的新数据库连接数,default : 3
c3p0.acquireIncrement=5

","more":"

#管理连接池的大小和连接的生存时间

\n

#maxIdleTime:连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接,即回收此连接。default : 0 单位 s
c3p0.maxIdleTime=600

\n

#idleConnectionTestPeriod:每900秒检查所有连接池中的空闲连接
c3p0.idleConnectionTestPeriod=900

\n

#配置PreparedStatement缓存

\n

#连接池为数据源缓存的PreparedStatement的总数。由于PreparedStatement属于单个Connection,所以这个数量应该根据应用中平均连接数乘以每个连接的平均PreparedStatement

\n

#来计算。同时maxStatementsPerConnection的配置无效。default : 0(不建议使用)
c3p0.maxStatements=500

\n

#连接池为数据源单个Connection缓存的PreparedStatement数,这个配置比maxStatements更有意义,因为它缓存的服务对象是单个数据连接,

\n

#如果设置的好,肯定是可以提高性能的。为0的时候不缓存。default : 0(看情况而论)
c3p0.maxStatementsPerConnection=30

\n

#重连相关配置

\n

#acquireRetryAttempts:连接池在获得新连接失败时重试的次数,如果小于等于0则无限重试直至连接获得成功。default : 30(建议使用)
c3p0.acquireRetryAttempts=5

\n

#acquireRetryDelay:两次连接中间隔时间,单位毫秒,连接池在获得新连接时的间隔时间。default : 1000 单位ms(建议使用)
c3p0.acquireRetryDelay=1000

\n

#breakAfterAcquireFailure:如果为true,则当连接获取失败时自动关闭数据源,除非重新启动应用程序。所以一般不用。default : false(不建议使用)
c3p0.breakAfterAcquireFailure=false

\n

#checkoutTimeout:配置当连接池所有连接用完时应用程序getConnection的等待时间。为0则无限等待直至有其他连接释放或者创建新的连接,

\n

#不为0则当时间到的时候如果仍没有获得连接,则会抛出SQLException。其实就是acquireRetryAttempts*acquireRetryDelay。default : 0(与上面两个,有重复,选择其中两个都行)
c3p0.checkoutTimeout=100

\n

#其他

\n

#autoCommitOnClose:连接池在回收数据库连接时是否自动提交事务。如果为false,则会回滚未提交的事务,如果为true,则会自动提交事务。default : false(不建议使用)
c3p0.autoCommitOnClose=false

\n

#c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3
c3p0.numHelperThreads=10

"},{"title":"dbcp, c3p0, druid工具类","comments":1,"description":null,"date":"2017-02-13T16:00:00.000Z","_content":"\n

DBCP工具类

\n\n
public class DBCPUtils {\n    private static DataSource dataSource = null;\n\n    static {\n        Properties properties = new Properties();\n        try {\n            properties.load(DBCPUtils.class.getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n            dataSource = BasicDataSourceFactory.createDataSource(properties);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    public DBCPUtils() {\n    }\n\n    public static Connection getConnetcion(){\n        Connection connection = null;\n        try {\n            connection = dataSource.getConnection();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion(Connection connection){\n        if(null != connection){\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n
\n\n\n\n #驱动\n driverClassName=com.mysql.jdbc.Driver\n #数据库连接地址\n url=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n #用户名\n username=root\n #密码\n password=anyao112233\n #初始化连接\n initialSize=10\n #连接池的最大数据库连接数。设为0表示无限制\n maxTotal=50\n ##最小空闲连接\n minIdle=10\n #最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n #空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\n maxIdle=50\n #超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\n removeAbandonedTimeout=60\n #超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\n removeAbandoned=true\n #最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\n maxWaitMillis=60000\n #在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\n timeBetweenEvictionRunsMillis=60000\n #连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\n minEvictableIdleTimeMillis=300000\n\n\n

C3P0工具类

\n\n
public class C3P0Utils {\n    private static DataSource dataSource = null;\n\n    private static ThreadLocal threadLocal = new ThreadLocal();\n\n    static {\n        //自动加载src下c3p0的配置文件【c3p0-config.xml】\n        dataSource = new ComboPooledDataSource(\"myApp\");\n    }\n\n    public C3P0Utils() {\n    }\n\t\t\n\tpublic static void beginTransaction(Connection connection) {\n\t\t\ttry {\n\t\t\t\t\tconnection.setAutoCommit(false);\n\t\t\t} catch (SQLException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t}\n\t}\n\n\tpublic static void commitTransaction(Connection connection) {\n\t\t\ttry {\n\t\t\t\t\tconnection.commit();\n\t\t\t} catch (SQLException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t}\n\t}\n\n    public static Connection getConnetcion() {\n        Connection connection = threadLocal.get();//得到当前线程上绑定的连接\n        try {\n            if (connection == null || !connection.isClosed()) {\n                //当前没有绑定连接\n                connection = dataSource.getConnection();//新建连接\n                threadLocal.set(connection);//将局部变量connection的值设置为conn\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion() {\n        Connection connection = threadLocal.get();\n        try {\n            if (null != connection && !connection.isClosed()) {\n                connection.close();\n                //从线程局部变量中移除conn,如果没有移除掉,下次还会用这个已经关闭的连接,就会出错\n                threadLocal.remove();\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n\n    }\n}\n
\n\n\n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n \n\n

DRUID工具类

\n\n\n","source":"_posts/2017_dcd.md","raw":"---\ntitle: dbcp, c3p0, druid工具类\n\ncomments: true \n\ntags: \n - DBCP\n - C3P0\n - DRUID\n - 工具类\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-14\n \n---\n\n

DBCP工具类

\n\n
public class DBCPUtils {\n    private static DataSource dataSource = null;\n\n    static {\n        Properties properties = new Properties();\n        try {\n            properties.load(DBCPUtils.class.getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n            dataSource = BasicDataSourceFactory.createDataSource(properties);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    public DBCPUtils() {\n    }\n\n    public static Connection getConnetcion(){\n        Connection connection = null;\n        try {\n            connection = dataSource.getConnection();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion(Connection connection){\n        if(null != connection){\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n
\n\n\n\n #驱动\n driverClassName=com.mysql.jdbc.Driver\n #数据库连接地址\n url=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n #用户名\n username=root\n #密码\n password=anyao112233\n #初始化连接\n initialSize=10\n #连接池的最大数据库连接数。设为0表示无限制\n maxTotal=50\n ##最小空闲连接\n minIdle=10\n #最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n #空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\n maxIdle=50\n #超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\n removeAbandonedTimeout=60\n #超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\n removeAbandoned=true\n #最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\n maxWaitMillis=60000\n #在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\n timeBetweenEvictionRunsMillis=60000\n #连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\n minEvictableIdleTimeMillis=300000\n\n\n

C3P0工具类

\n\n
public class C3P0Utils {\n    private static DataSource dataSource = null;\n\n    private static ThreadLocal threadLocal = new ThreadLocal();\n\n    static {\n        //自动加载src下c3p0的配置文件【c3p0-config.xml】\n        dataSource = new ComboPooledDataSource(\"myApp\");\n    }\n\n    public C3P0Utils() {\n    }\n\t\t\n\tpublic static void beginTransaction(Connection connection) {\n\t\t\ttry {\n\t\t\t\t\tconnection.setAutoCommit(false);\n\t\t\t} catch (SQLException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t}\n\t}\n\n\tpublic static void commitTransaction(Connection connection) {\n\t\t\ttry {\n\t\t\t\t\tconnection.commit();\n\t\t\t} catch (SQLException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t}\n\t}\n\n    public static Connection getConnetcion() {\n        Connection connection = threadLocal.get();//得到当前线程上绑定的连接\n        try {\n            if (connection == null || !connection.isClosed()) {\n                //当前没有绑定连接\n                connection = dataSource.getConnection();//新建连接\n                threadLocal.set(connection);//将局部变量connection的值设置为conn\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion() {\n        Connection connection = threadLocal.get();\n        try {\n            if (null != connection && !connection.isClosed()) {\n                connection.close();\n                //从线程局部变量中移除conn,如果没有移除掉,下次还会用这个已经关闭的连接,就会出错\n                threadLocal.remove();\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n\n    }\n}\n
\n\n\n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n \n\n

DRUID工具类

\n\n\n","slug":"2017_dcd","published":1,"updated":"2018-01-13T02:29:22.240Z","layout":"post","photos":[],"link":"","_id":"ckm3inuw8002u24ujdh9avtar","content":"

DBCP工具类

\n\n
public class DBCPUtils {\n    private static DataSource dataSource = null;\n\n    static {\n        Properties properties = new Properties();\n        try {\n            properties.load(DBCPUtils.class.getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n            dataSource = BasicDataSourceFactory.createDataSource(properties);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    public DBCPUtils() {\n    }\n\n    public static Connection getConnetcion(){\n        Connection connection = null;\n        try {\n            connection = dataSource.getConnection();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion(Connection connection){\n        if(null != connection){\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n
\n\n\n
#驱动\ndriverClassName=com.mysql.jdbc.Driver\n#数据库连接地址\nurl=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n#用户名\nusername=root\n#密码\npassword=anyao112233\n#初始化连接\ninitialSize=10\n#连接池的最大数据库连接数。设为0表示无限制\nmaxTotal=50\n##最小空闲连接\nminIdle=10\n#最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n#空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\nmaxIdle=50\n#超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\nremoveAbandonedTimeout=60\n#超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\nremoveAbandoned=true\n#最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\nmaxWaitMillis=60000\n#在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\ntimeBetweenEvictionRunsMillis=60000\n#连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\nminEvictableIdleTimeMillis=300000\n

C3P0工具类

\n\n
public class C3P0Utils {\n    private static DataSource dataSource = null;\n\n    private static ThreadLocal threadLocal = new ThreadLocal();\n\n    static {\n        //自动加载src下c3p0的配置文件【c3p0-config.xml】\n        dataSource = new ComboPooledDataSource(\"myApp\");\n    }\n\n    public C3P0Utils() {\n    }\n\n    public static void beginTransaction(Connection connection) {\n            try {\n                    connection.setAutoCommit(false);\n            } catch (SQLException e) {\n                    e.printStackTrace();\n            }\n    }\n\n    public static void commitTransaction(Connection connection) {\n            try {\n                    connection.commit();\n            } catch (SQLException e) {\n                    e.printStackTrace();\n            }\n    }\n\n    public static Connection getConnetcion() {\n        Connection connection = threadLocal.get();//得到当前线程上绑定的连接\n        try {\n            if (connection == null || !connection.isClosed()) {\n                //当前没有绑定连接\n                connection = dataSource.getConnection();//新建连接\n                threadLocal.set(connection);//将局部变量connection的值设置为conn\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion() {\n        Connection connection = threadLocal.get();\n        try {\n            if (null != connection && !connection.isClosed()) {\n                connection.close();\n                //从线程局部变量中移除conn,如果没有移除掉,下次还会用这个已经关闭的连接,就会出错\n                threadLocal.remove();\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n\n    }\n}\n
\n\n\n
    <c3p0-config>\n    <default-config>\n        <property name="user">root</property>\n        <property name="password">anyao112233</property>\n        <property name="driverClass">com.mysql.jdbc.Driver</property>\n        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n        <property name="initialPoolSize">10</property>\n        <property name="maxIdleTime">30</property>\n        <property name="maxPoolSize">100</property>\n        <property name="minPoolSize">10</property>\n    </default-config>\n\n    <named-config name="myApp">\n        <property name="user">root</property>\n        <property name="password">anyao112233</property>\n        <property name="driverClass">com.mysql.jdbc.Driver</property>\n        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n        <property name="initialPoolSize">10</property>\n        <property name="maxIdleTime">30</property>\n        <property name="maxPoolSize">100</property>\n        <property name="minPoolSize">10</property>\n    </named-config>\n</c3p0-config>\n

DRUID工具类

\n\n

\n","site":{"data":{}},"excerpt":"

DBCP工具类

\n\n
public class DBCPUtils {\n    private static DataSource dataSource = null;\n\n    static {\n        Properties properties = new Properties();\n        try {\n            properties.load(DBCPUtils.class.getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n            dataSource = BasicDataSourceFactory.createDataSource(properties);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n    public DBCPUtils() {\n    }\n\n    public static Connection getConnetcion(){\n        Connection connection = null;\n        try {\n            connection = dataSource.getConnection();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion(Connection connection){\n        if(null != connection){\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n
","more":"
#驱动\ndriverClassName=com.mysql.jdbc.Driver\n#数据库连接地址\nurl=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n#用户名\nusername=root\n#密码\npassword=anyao112233\n#初始化连接\ninitialSize=10\n#连接池的最大数据库连接数。设为0表示无限制\nmaxTotal=50\n##最小空闲连接\nminIdle=10\n#最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n#空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\nmaxIdle=50\n#超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\nremoveAbandonedTimeout=60\n#超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\nremoveAbandoned=true\n#最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\nmaxWaitMillis=60000\n#在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\ntimeBetweenEvictionRunsMillis=60000\n#连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\nminEvictableIdleTimeMillis=300000\n

C3P0工具类

\n\n
public class C3P0Utils {\n    private static DataSource dataSource = null;\n\n    private static ThreadLocal threadLocal = new ThreadLocal();\n\n    static {\n        //自动加载src下c3p0的配置文件【c3p0-config.xml】\n        dataSource = new ComboPooledDataSource(\"myApp\");\n    }\n\n    public C3P0Utils() {\n    }\n\n    public static void beginTransaction(Connection connection) {\n            try {\n                    connection.setAutoCommit(false);\n            } catch (SQLException e) {\n                    e.printStackTrace();\n            }\n    }\n\n    public static void commitTransaction(Connection connection) {\n            try {\n                    connection.commit();\n            } catch (SQLException e) {\n                    e.printStackTrace();\n            }\n    }\n\n    public static Connection getConnetcion() {\n        Connection connection = threadLocal.get();//得到当前线程上绑定的连接\n        try {\n            if (connection == null || !connection.isClosed()) {\n                //当前没有绑定连接\n                connection = dataSource.getConnection();//新建连接\n                threadLocal.set(connection);//将局部变量connection的值设置为conn\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return connection;\n    }\n\n    public static void releaseConnecion() {\n        Connection connection = threadLocal.get();\n        try {\n            if (null != connection && !connection.isClosed()) {\n                connection.close();\n                //从线程局部变量中移除conn,如果没有移除掉,下次还会用这个已经关闭的连接,就会出错\n                threadLocal.remove();\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n\n    }\n}\n
\n\n\n
    <c3p0-config>\n    <default-config>\n        <property name="user">root</property>\n        <property name="password">anyao112233</property>\n        <property name="driverClass">com.mysql.jdbc.Driver</property>\n        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n        <property name="initialPoolSize">10</property>\n        <property name="maxIdleTime">30</property>\n        <property name="maxPoolSize">100</property>\n        <property name="minPoolSize">10</property>\n    </default-config>\n\n    <named-config name="myApp">\n        <property name="user">root</property>\n        <property name="password">anyao112233</property>\n        <property name="driverClass">com.mysql.jdbc.Driver</property>\n        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n        <property name="initialPoolSize">10</property>\n        <property name="maxIdleTime">30</property>\n        <property name="maxPoolSize">100</property>\n        <property name="minPoolSize">10</property>\n    </named-config>\n</c3p0-config>\n

DRUID工具类

\n\n

"},{"title":"Vue学习笔记","comments":1,"description":null,"date":"2017-02-28T16:00:00.000Z","_content":"\n## Vue.js 是一套构建用户界面的 渐进式框架 JavaScript MVVM库 它是以数据驱动和组件化的思想构建的,无需手动操作DOM.\n\n# MVVM模式\n\n## Model-View-ViewModel\n\nViewModel是Vue.js的核心,它是一个Vue实例。Vue实例是作用于某一个HTML元素上的,这个元素可以是HTML的body元素,也可以是指定了id的某个元素。\n\n当创建了ViewModel后,双向绑定是如何达成的呢?\n\n首先,我们将DOM Listeners和Data Bindings看作两个工具,它们是实现双向绑定的关键。\n从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;\n从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。\n<摘自: http://www.cnblogs.com/rik28/p/6024425.html>","source":"_posts/2017_e.md","raw":"---\ntitle: Vue学习笔记\n\ncomments: true \n\ntags: \n - Vue\n\ncategories: \n - 框架相关\n - 前端技术\n\ndescription:\n\ndate: 2017-03-1\n \n---\n\n## Vue.js 是一套构建用户界面的 渐进式框架 JavaScript MVVM库 它是以数据驱动和组件化的思想构建的,无需手动操作DOM.\n\n# MVVM模式\n\n## Model-View-ViewModel\n\nViewModel是Vue.js的核心,它是一个Vue实例。Vue实例是作用于某一个HTML元素上的,这个元素可以是HTML的body元素,也可以是指定了id的某个元素。\n\n当创建了ViewModel后,双向绑定是如何达成的呢?\n\n首先,我们将DOM Listeners和Data Bindings看作两个工具,它们是实现双向绑定的关键。\n从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;\n从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。\n<摘自: http://www.cnblogs.com/rik28/p/6024425.html>","slug":"2017_e","published":1,"updated":"2018-01-13T02:29:22.234Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwa002y24ujc71e82is","content":"

Vue.js 是一套构建用户界面的 渐进式框架 JavaScript MVVM库 它是以数据驱动和组件化的思想构建的,无需手动操作DOM.

MVVM模式

Model-View-ViewModel

ViewModel是Vue.js的核心,它是一个Vue实例。Vue实例是作用于某一个HTML元素上的,这个元素可以是HTML的body元素,也可以是指定了id的某个元素。

\n

当创建了ViewModel后,双向绑定是如何达成的呢?

\n

首先,我们将DOM Listeners和Data Bindings看作两个工具,它们是实现双向绑定的关键。
从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;
从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。
<摘自: http://www.cnblogs.com/rik28/p/6024425.html>

\n","site":{"data":{}},"excerpt":"","more":"

Vue.js 是一套构建用户界面的 渐进式框架 JavaScript MVVM库 它是以数据驱动和组件化的思想构建的,无需手动操作DOM.

MVVM模式

Model-View-ViewModel

ViewModel是Vue.js的核心,它是一个Vue实例。Vue实例是作用于某一个HTML元素上的,这个元素可以是HTML的body元素,也可以是指定了id的某个元素。

\n

当创建了ViewModel后,双向绑定是如何达成的呢?

\n

首先,我们将DOM Listeners和Data Bindings看作两个工具,它们是实现双向绑定的关键。
从View侧看,ViewModel中的DOM Listeners工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;
从Model侧看,当我们更新Model中的数据时,Data Bindings工具会帮我们更新页面中的DOM元素。
<摘自: http://www.cnblogs.com/rik28/p/6024425.html>

\n"},{"title":"1.了解java","comments":1,"description":null,"date":"2017-02-04T16:00:00.000Z","_content":"\n\n来自<<深入了解java虚拟机>>:\nJava不仅是一门编程语言, 也是一个由一系列计算机软件和规范形成的技术体系\n--------------------------------------\n\n***\n\n##### **java技术体系包括** \n##### (这是sun公司定义的)\n\n> 1. Java程序设计语言\n> 2. 各种硬件平台上的java虚拟机\n> 3. Java API类库\n> 4. Class文件\n> 5. 各种第三方Java类库\n\n>其中前三部分共同统称------>JDK (Java Development Kit) 这是支持Java程序开发的最小环境*\n>JavaAPI类库中的JavaSE API子集和Java虚拟机统称------>JRE (Java Runtime Environment) 这是支持Java程序运行的标准环境*\n![这里写图片描述](http://img.blog.csdn.net/20161214190258788?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW55YW8xMTIyMzM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n\n\n *(这是原书上的附图)*\n----------\n## *java技术体系按照业务领域目前分为4个平台**\n\n> 1. Java Card ------> 支持Java小程序(Applets) 运行在小内存设备(智能卡)上的平台\n> 2. Java ME (Micro Edition) -----> 支持Java程序运行在移动终端(手机, PDA)上的平台, 也称为J2ME\n> 3. Java SE (Standard Edition) --> 支持面向桌面的级应用(如windows下的应用程序)的Java平台 提供了完整的Java核心API, 也称为J2SE\n> 4. Java EE (Enterprise Edition) --> 支持使用多层架构的企业应用的Java平台, 提供了JavaSE API以及其它扩充,也称为J2EE .\n\n\n----------\n#### *Java发展史**\n![这里写图片描述](http://img.blog.csdn.net/20161214192716089?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW55YW8xMTIyMzM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n**其他大事**\n\n\n - 1999年4月,HotSpot虚拟机发布 (其原虚拟机研发公司于1997年被sun公司收购),JDK1.3以后成为所有SunJDK的默认虚拟机\n - JDK1.5以前版本语法变化较小,发布的1.5的语法层面进行了巨大改进,包括自动装箱,泛型,动态注解,foreach等,并改进了java内存模型\n - JDK1.6以后就不叫J2ME,J2SE,J2EE而改名为Java ME6, Java SE6, Java EE6\n - 2006年在JavaOne大会上 Sun公司将Java陆续开源\n - 2009年Sun公司被Oracle收购 (但Java语言由JCP组织管理)\n - \n\n\n----------\n### *Java虚拟机发展史(部分)**\n虚拟机版本有\n 1. Sun Classic VM -->世界上第一个商用虚拟机 (JDK1.0的运行环境)\n 2. Sun HotSpot VM -->目前使用范围最广的Java虚拟机 ,Sun JDK,Open JDK所带的虚拟机 :: (#当初设计 的目标是达到C语言50%以上的执行效率)\n 3. KVM (Sun公司) -->简单,轻量,高度可移植,运行速度慢.广泛运用于Android,iOS等智能手机系统\n 4. JRockit VM (BEA公司) -->专注服务器应用的虚拟机,所以可以不关注启动速度而运行速度快,其在垃圾回收器和MissionControl服务套件等部分的实现处于领先地位\n 5. IBM J9 VM (IBM公司) --> 一款多用途虚拟机\n 6. Microsoft JVM(微软) --> 这是可以说是最有意思的......当初微软也是Java技术的铁杆支持者,并且自行开发了只有win平台的java虚拟机,然而Sun公司起诉微软侵权,微软败诉于是被迫终止了Java虚拟机的研究,移除了WindowsXP中自家Java虚拟机.有趣的是当初怼人家时候说人侵权要阻止人家支持Java,真的成了之后Sun公司又到处登报纸希望Windows继续支持Java,因为那时候Sun真的是已经日薄西山了 (讽刺啊!!)\n\n\n----------\n#### **OpenJDK源码仓库地址***\nhttp://hg.openjdk.java.net/jdk7u/jdk7u-dev\n\n#### **OpenJDK官方源码包**\nhttp://jdk7.java.net/source.html\n\n*ps:尽量在linux或者mac上构建OpenJDK*","source":"_posts/2017_java.md","raw":"---\ntitle: 1.了解java\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n\n来自<<深入了解java虚拟机>>:\nJava不仅是一门编程语言, 也是一个由一系列计算机软件和规范形成的技术体系\n--------------------------------------\n\n***\n\n##### **java技术体系包括** \n##### (这是sun公司定义的)\n\n> 1. Java程序设计语言\n> 2. 各种硬件平台上的java虚拟机\n> 3. Java API类库\n> 4. Class文件\n> 5. 各种第三方Java类库\n\n>其中前三部分共同统称------>JDK (Java Development Kit) 这是支持Java程序开发的最小环境*\n>JavaAPI类库中的JavaSE API子集和Java虚拟机统称------>JRE (Java Runtime Environment) 这是支持Java程序运行的标准环境*\n![这里写图片描述](http://img.blog.csdn.net/20161214190258788?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW55YW8xMTIyMzM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n\n\n *(这是原书上的附图)*\n----------\n## *java技术体系按照业务领域目前分为4个平台**\n\n> 1. Java Card ------> 支持Java小程序(Applets) 运行在小内存设备(智能卡)上的平台\n> 2. Java ME (Micro Edition) -----> 支持Java程序运行在移动终端(手机, PDA)上的平台, 也称为J2ME\n> 3. Java SE (Standard Edition) --> 支持面向桌面的级应用(如windows下的应用程序)的Java平台 提供了完整的Java核心API, 也称为J2SE\n> 4. Java EE (Enterprise Edition) --> 支持使用多层架构的企业应用的Java平台, 提供了JavaSE API以及其它扩充,也称为J2EE .\n\n\n----------\n#### *Java发展史**\n![这里写图片描述](http://img.blog.csdn.net/20161214192716089?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYW55YW8xMTIyMzM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\n\n**其他大事**\n\n\n - 1999年4月,HotSpot虚拟机发布 (其原虚拟机研发公司于1997年被sun公司收购),JDK1.3以后成为所有SunJDK的默认虚拟机\n - JDK1.5以前版本语法变化较小,发布的1.5的语法层面进行了巨大改进,包括自动装箱,泛型,动态注解,foreach等,并改进了java内存模型\n - JDK1.6以后就不叫J2ME,J2SE,J2EE而改名为Java ME6, Java SE6, Java EE6\n - 2006年在JavaOne大会上 Sun公司将Java陆续开源\n - 2009年Sun公司被Oracle收购 (但Java语言由JCP组织管理)\n - \n\n\n----------\n### *Java虚拟机发展史(部分)**\n虚拟机版本有\n 1. Sun Classic VM -->世界上第一个商用虚拟机 (JDK1.0的运行环境)\n 2. Sun HotSpot VM -->目前使用范围最广的Java虚拟机 ,Sun JDK,Open JDK所带的虚拟机 :: (#当初设计 的目标是达到C语言50%以上的执行效率)\n 3. KVM (Sun公司) -->简单,轻量,高度可移植,运行速度慢.广泛运用于Android,iOS等智能手机系统\n 4. JRockit VM (BEA公司) -->专注服务器应用的虚拟机,所以可以不关注启动速度而运行速度快,其在垃圾回收器和MissionControl服务套件等部分的实现处于领先地位\n 5. IBM J9 VM (IBM公司) --> 一款多用途虚拟机\n 6. Microsoft JVM(微软) --> 这是可以说是最有意思的......当初微软也是Java技术的铁杆支持者,并且自行开发了只有win平台的java虚拟机,然而Sun公司起诉微软侵权,微软败诉于是被迫终止了Java虚拟机的研究,移除了WindowsXP中自家Java虚拟机.有趣的是当初怼人家时候说人侵权要阻止人家支持Java,真的成了之后Sun公司又到处登报纸希望Windows继续支持Java,因为那时候Sun真的是已经日薄西山了 (讽刺啊!!)\n\n\n----------\n#### **OpenJDK源码仓库地址***\nhttp://hg.openjdk.java.net/jdk7u/jdk7u-dev\n\n#### **OpenJDK官方源码包**\nhttp://jdk7.java.net/source.html\n\n*ps:尽量在linux或者mac上构建OpenJDK*","slug":"2017_java","published":1,"updated":"2018-01-13T02:29:22.242Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwc003124ujs7tfgb05","content":"

来自<<深入了解java虚拟机>>:

\n

Java不仅是一门编程语言, 也是一个由一系列计算机软件和规范形成的技术体系


\n
java技术体系包括
(这是sun公司定义的)
\n
    \n
  1. Java程序设计语言
  2. \n
  3. 各种硬件平台上的java虚拟机
  4. \n
  5. Java API类库
  6. \n
  7. Class文件
  8. \n
  9. 各种第三方Java类库
  10. \n
\n
\n
\n

其中前三部分共同统称——>JDK (Java Development Kit) 这是支持Java程序开发的最小环境
JavaAPI类库中的JavaSE API子集和Java虚拟机统称——>JRE (Java Runtime Environment) 这是支持Java程序运行的标准环境

\"这里写图片描述\"

\n
\n\n
*(这是原书上的附图)*\n

\n

*java技术体系按照业务领域目前分为4个平台**

\n
    \n
  1. Java Card ——> 支持Java小程序(Applets) 运行在小内存设备(智能卡)上的平台
  2. \n
  3. Java ME (Micro Edition) —–> 支持Java程序运行在移动终端(手机, PDA)上的平台, 也称为J2ME
  4. \n
  5. Java SE (Standard Edition) –> 支持面向桌面的级应用(如windows下的应用程序)的Java平台 提供了完整的Java核心API, 也称为J2SE
  6. \n
  7. Java EE (Enterprise Edition) –> 支持使用多层架构的企业应用的Java平台, 提供了JavaSE API以及其它扩充,也称为J2EE .
  8. \n
\n
\n
\n

*Java发展史**

\"这里写图片描述\"

\n

其他大事

\n
    \n
  • 1999年4月,HotSpot虚拟机发布 (其原虚拟机研发公司于1997年被sun公司收购),JDK1.3以后成为所有SunJDK的默认虚拟机
  • \n
  • JDK1.5以前版本语法变化较小,发布的1.5的语法层面进行了巨大改进,包括自动装箱,泛型,动态注解,foreach等,并改进了java内存模型
  • \n
  • JDK1.6以后就不叫J2ME,J2SE,J2EE而改名为Java ME6, Java SE6, Java EE6
  • \n
  • 2006年在JavaOne大会上 Sun公司将Java陆续开源
  • \n
  • 2009年Sun公司被Oracle收购 (但Java语言由JCP组织管理)
  • \n
  • \n
\n
\n

*Java虚拟机发展史(部分)**

虚拟机版本有

\n
    \n
  1. Sun Classic VM –>世界上第一个商用虚拟机 (JDK1.0的运行环境)
  2. \n
  3. Sun HotSpot VM –>目前使用范围最广的Java虚拟机 ,Sun JDK,Open JDK所带的虚拟机 :: (#当初设计 的目标是达到C语言50%以上的执行效率)
  4. \n
  5. KVM (Sun公司) –>简单,轻量,高度可移植,运行速度慢.广泛运用于Android,iOS等智能手机系统
  6. \n
  7. JRockit VM (BEA公司) –>专注服务器应用的虚拟机,所以可以不关注启动速度而运行速度快,其在垃圾回收器和MissionControl服务套件等部分的实现处于领先地位
  8. \n
  9. IBM J9 VM (IBM公司) –> 一款多用途虚拟机
  10. \n
  11. Microsoft JVM(微软) –> 这是可以说是最有意思的……当初微软也是Java技术的铁杆支持者,并且自行开发了只有win平台的java虚拟机,然而Sun公司起诉微软侵权,微软败诉于是被迫终止了Java虚拟机的研究,移除了WindowsXP中自家Java虚拟机.有趣的是当初怼人家时候说人侵权要阻止人家支持Java,真的成了之后Sun公司又到处登报纸希望Windows继续支持Java,因为那时候Sun真的是已经日薄西山了 (讽刺啊!!)
  12. \n
\n
\n

OpenJDK源码仓库地址*

http://hg.openjdk.java.net/jdk7u/jdk7u-dev

\n

OpenJDK官方源码包

http://jdk7.java.net/source.html

\n

ps:尽量在linux或者mac上构建OpenJDK

\n","site":{"data":{}},"excerpt":"

来自<<深入了解java虚拟机>>:

\n

Java不仅是一门编程语言, 也是一个由一系列计算机软件和规范形成的技术体系


\n
java技术体系包括
(这是sun公司定义的)
\n
    \n
  1. Java程序设计语言
  2. \n
  3. 各种硬件平台上的java虚拟机
  4. \n
  5. Java API类库
  6. \n
  7. Class文件
  8. \n
  9. 各种第三方Java类库
  10. \n
\n
\n
\n

其中前三部分共同统称——>JDK (Java Development Kit) 这是支持Java程序开发的最小环境
JavaAPI类库中的JavaSE API子集和Java虚拟机统称——>JRE (Java Runtime Environment) 这是支持Java程序运行的标准环境

\"这里写图片描述\"

\n
","more":"
*(这是原书上的附图)*\n

\n

*java技术体系按照业务领域目前分为4个平台**

\n
    \n
  1. Java Card ——> 支持Java小程序(Applets) 运行在小内存设备(智能卡)上的平台
  2. \n
  3. Java ME (Micro Edition) —–> 支持Java程序运行在移动终端(手机, PDA)上的平台, 也称为J2ME
  4. \n
  5. Java SE (Standard Edition) –> 支持面向桌面的级应用(如windows下的应用程序)的Java平台 提供了完整的Java核心API, 也称为J2SE
  6. \n
  7. Java EE (Enterprise Edition) –> 支持使用多层架构的企业应用的Java平台, 提供了JavaSE API以及其它扩充,也称为J2EE .
  8. \n
\n
\n
\n

*Java发展史**

\"这里写图片描述\"

\n

其他大事

\n
    \n
  • 1999年4月,HotSpot虚拟机发布 (其原虚拟机研发公司于1997年被sun公司收购),JDK1.3以后成为所有SunJDK的默认虚拟机
  • \n
  • JDK1.5以前版本语法变化较小,发布的1.5的语法层面进行了巨大改进,包括自动装箱,泛型,动态注解,foreach等,并改进了java内存模型
  • \n
  • JDK1.6以后就不叫J2ME,J2SE,J2EE而改名为Java ME6, Java SE6, Java EE6
  • \n
  • 2006年在JavaOne大会上 Sun公司将Java陆续开源
  • \n
  • 2009年Sun公司被Oracle收购 (但Java语言由JCP组织管理)
  • \n
  • \n
\n
\n

*Java虚拟机发展史(部分)**

虚拟机版本有

\n
    \n
  1. Sun Classic VM –>世界上第一个商用虚拟机 (JDK1.0的运行环境)
  2. \n
  3. Sun HotSpot VM –>目前使用范围最广的Java虚拟机 ,Sun JDK,Open JDK所带的虚拟机 :: (#当初设计 的目标是达到C语言50%以上的执行效率)
  4. \n
  5. KVM (Sun公司) –>简单,轻量,高度可移植,运行速度慢.广泛运用于Android,iOS等智能手机系统
  6. \n
  7. JRockit VM (BEA公司) –>专注服务器应用的虚拟机,所以可以不关注启动速度而运行速度快,其在垃圾回收器和MissionControl服务套件等部分的实现处于领先地位
  8. \n
  9. IBM J9 VM (IBM公司) –> 一款多用途虚拟机
  10. \n
  11. Microsoft JVM(微软) –> 这是可以说是最有意思的……当初微软也是Java技术的铁杆支持者,并且自行开发了只有win平台的java虚拟机,然而Sun公司起诉微软侵权,微软败诉于是被迫终止了Java虚拟机的研究,移除了WindowsXP中自家Java虚拟机.有趣的是当初怼人家时候说人侵权要阻止人家支持Java,真的成了之后Sun公司又到处登报纸希望Windows继续支持Java,因为那时候Sun真的是已经日薄西山了 (讽刺啊!!)
  12. \n
\n
\n

OpenJDK源码仓库地址*

http://hg.openjdk.java.net/jdk7u/jdk7u-dev

\n

OpenJDK官方源码包

http://jdk7.java.net/source.html

\n

ps:尽量在linux或者mac上构建OpenJDK

"},{"title":"jaxp解析XML","comments":1,"description":"jaxp解析xml文档","date":"2017-02-04T16:00:00.000Z","_content":"\n```java\npackage cn.utils;\n//包名\n\nimport java.io.FileOutputStream;\n\nimport javax.xml.crypto.dsig.Transform;\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerConfigurationException;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\n\n//工具类 执行那些重复的代码块 默认为静态\npublic class XmlUtils {\n private static String filename = \"src/exam.xml\";\n\n //得到解析器并解析xml文档\n public static Document getDocument() throws Exception{\n //1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n //2.从DOM工厂获得DOM解析器 有了这个实例才可以解析\n DocumentBuilder builder = factory.newDocumentBuilder();\n //3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n //4.以后的处理都是对Document对象进行的\n\n return document;\n }\n\n //把得到的数据写入xml文档\n public static void write2Xml(Document document) throws Exception{\n TransformerFactory factory = TransformerFactory.newInstance();//产生转化器\n Transformer tf = factory.newTransformer();\n tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream(filename)));\n\n //原理\n /*\n //创建工厂实例\n TransformerFactory tf = TransformerFactory.newInstance();\n //通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)\n Transformer tr = tf.newTransformer();\n //DOMSource是Source的实现类 把Document类型封装为Source类型\n Source s = new DOMSource(document);\n //声明输出流对象 指向硬盘中的XML文件\n OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");\n //把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件\n Result r = new StreamResult(f);\n //transform方法(来源, 目的地) 把s写入r\n tr.transform(s, r);\n\n\n */\n }\n}\n```\n","source":"_posts/2017_jaxp2.md","raw":"---\ntitle: jaxp解析XML\n\ncomments: true \n\ntags: jaxp\n\ncategories: \n - DOM操作 \n - XML\n\ndescription: jaxp解析xml文档\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n```java\npackage cn.utils;\n//包名\n\nimport java.io.FileOutputStream;\n\nimport javax.xml.crypto.dsig.Transform;\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerConfigurationException;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\n\n//工具类 执行那些重复的代码块 默认为静态\npublic class XmlUtils {\n private static String filename = \"src/exam.xml\";\n\n //得到解析器并解析xml文档\n public static Document getDocument() throws Exception{\n //1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n //2.从DOM工厂获得DOM解析器 有了这个实例才可以解析\n DocumentBuilder builder = factory.newDocumentBuilder();\n //3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n //4.以后的处理都是对Document对象进行的\n\n return document;\n }\n\n //把得到的数据写入xml文档\n public static void write2Xml(Document document) throws Exception{\n TransformerFactory factory = TransformerFactory.newInstance();//产生转化器\n Transformer tf = factory.newTransformer();\n tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream(filename)));\n\n //原理\n /*\n //创建工厂实例\n TransformerFactory tf = TransformerFactory.newInstance();\n //通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)\n Transformer tr = tf.newTransformer();\n //DOMSource是Source的实现类 把Document类型封装为Source类型\n Source s = new DOMSource(document);\n //声明输出流对象 指向硬盘中的XML文件\n OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");\n //把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件\n Result r = new StreamResult(f);\n //transform方法(来源, 目的地) 把s写入r\n tr.transform(s, r);\n\n\n */\n }\n}\n```\n","slug":"2017_jaxp2","published":1,"updated":"2018-01-13T02:29:22.245Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwd003424uj6pwzwimy","content":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package cn.utils;
//包名

import java.io.FileOutputStream;

import javax.xml.crypto.dsig.Transform;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

//工具类 执行那些重复的代码块 默认为静态
public class XmlUtils {
private static String filename = \"src/exam.xml\";

//得到解析器并解析xml文档
public static Document getDocument() throws Exception{
//1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//2.从DOM工厂获得DOM解析器 有了这个实例才可以解析
DocumentBuilder builder = factory.newDocumentBuilder();
//3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象
Document document = builder.parse(\"src/cn/xml/book.xml\");
//4.以后的处理都是对Document对象进行的

return document;
}

//把得到的数据写入xml文档
public static void write2Xml(Document document) throws Exception{
TransformerFactory factory = TransformerFactory.newInstance();//产生转化器
Transformer tf = factory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream(filename)));

//原理
/*
//创建工厂实例
TransformerFactory tf = TransformerFactory.newInstance();
//通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)
Transformer tr = tf.newTransformer();
//DOMSource是Source的实现类 把Document类型封装为Source类型
Source s = new DOMSource(document);
//声明输出流对象 指向硬盘中的XML文件
OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");
//把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件
Result r = new StreamResult(f);
//transform方法(来源, 目的地) 把s写入r
tr.transform(s, r);


*/
}
}
\n","site":{"data":{}},"excerpt":"","more":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package cn.utils;
//包名

import java.io.FileOutputStream;

import javax.xml.crypto.dsig.Transform;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

//工具类 执行那些重复的代码块 默认为静态
public class XmlUtils {
private static String filename = \"src/exam.xml\";

//得到解析器并解析xml文档
public static Document getDocument() throws Exception{
//1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//2.从DOM工厂获得DOM解析器 有了这个实例才可以解析
DocumentBuilder builder = factory.newDocumentBuilder();
//3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象
Document document = builder.parse(\"src/cn/xml/book.xml\");
//4.以后的处理都是对Document对象进行的

return document;
}

//把得到的数据写入xml文档
public static void write2Xml(Document document) throws Exception{
TransformerFactory factory = TransformerFactory.newInstance();//产生转化器
Transformer tf = factory.newTransformer();
tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream(filename)));

//原理
/*
//创建工厂实例
TransformerFactory tf = TransformerFactory.newInstance();
//通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)
Transformer tr = tf.newTransformer();
//DOMSource是Source的实现类 把Document类型封装为Source类型
Source s = new DOMSource(document);
//声明输出流对象 指向硬盘中的XML文件
OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");
//把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件
Result r = new StreamResult(f);
//transform方法(来源, 目的地) 把s写入r
tr.transform(s, r);


*/
}
}
\n"},{"title":"JDBC把文件作为数据对数据库的操作简单示例(Blob类型)","comments":1,"description":null,"date":"2017-02-09T16:00:00.000Z","_content":"\n# :如果是插入某个文件可以用mysql的Blob类型\n\n#### *Blob类型,二进制大对象,用来存储二进制文件(图片等)*\n下面是简单的示例\n\n\n\n```java\npackage com.jdbc;\n\nimport org.junit.Test;\n\nimport java.io.*;\nimport java.sql.*;\nimport java.util.Properties;\n\n/**\n * Created by zj on 2017/2/10.\n */\npublic class insertBlob {\n @Test\n public void test() {\n InputStream inputStream = null;\n /*\n\t\t插入BLOB类型数据 (必须使用PreparedStatement)\n */\n\t\ttry {\n inputStream = new FileInputStream(\"E:/app/Project/Test_Demo/src/java.jpg\");\n insert(\"insert into teacher(name, birth, picture) values (?, ?, ?)\",\n \"fucc\", new Date(System.currentTimeMillis()), inputStream);\n } catch (FileNotFoundException e) {\n e.printStackTrace();\n } finally {\n if (inputStream != null) {\n try {\n inputStream.close();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n }\n\n /*\n 查询并展示Blob类型数据\n */\n query(\"SELECT id id, name name, birth birth, picture picture FROM teacher WHERE id = ?\",\n 442);\n }\n\n\t//插入方法(其实删除,修改也可以使用)\n\t//插入Blob类型(mysql)需要传入InputStream类型\n public int insert(String sql, Object... args) {\n Connection connection = null;\n PreparedStatement state = null;\n int result = 0;\n\n try {\n connection = getConnection();\n state = connection.prepareStatement(sql);\n for (int i = 0; i < args.length; i++) {\n //专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);\n state.setObject(i + 1, args[i]); //遍历设置占位符的值\n }\n result = state.executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close(connection, state, null);\n }\n\n return result;\n }\n\n\t//读取 读取Blob类型时, 需要用InputStream类型接收在用OutputStream读取到文件中方可访问\n public void query(String sql, Object... args) {\n Connection connection = null;\n PreparedStatement state = null;\n ResultSet resultSet = null;\n InputStream inputStream = null;\n OutputStream outputStream = null;\n\n try {\n connection = getConnection();\n state = connection.prepareStatement(sql);\n for (int i = 0; i < args.length; i++) {\n //专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);\n state.setObject(i + 1, args[i]); //遍历设置占位符的值\n }\n resultSet = state.executeQuery();\n while (resultSet.next()) {\n int id = resultSet.getInt(1);\n String name = resultSet.getString(2);\n Date date = resultSet.getDate(3);\n Blob picture = resultSet.getBlob(4);\n System.out.println(id + \" \" + name + \" \" + date + \" \" + picture);\n\n\t\t\t\t//读取文件(Blob->InputStream->OutputStream->文件)\n\t\t\t\tinputStream = picture.getBinaryStream();\n outputStream = new FileOutputStream(\"pic.jpg\");\n byte[] bytes = new byte[50];\n int len;\n while ((len = inputStream.read(bytes)) != -1) {\n outputStream.write(bytes, 0, len);\n }\n }\n } catch (SQLException | IOException e) {\n e.printStackTrace();\n } finally {\n if(outputStream != null){\n try {\n outputStream.close();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n if(inputStream != null){\n try {\n inputStream.close();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n\n close(connection, state, resultSet);\n }\n }\n\n private Connection getConnection() {\n Connection connection = null;\n try {\n Properties properties = new Properties();\n //读取文件中的数据库配置信息赋值给各个变量\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n\n Class.forName(dirverClass);\n connection = DriverManager.getConnection(url, user, password);\n } catch (SQLException | IOException | ReflectiveOperationException e) {\n e.printStackTrace();\n }\n\n return connection;\n }\n\n private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (statement != null) {\n try {\n statement.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n}\n\n```","source":"_posts/2017_jdbc_file.md","raw":"---\ntitle: JDBC把文件作为数据对数据库的操作简单示例(Blob类型)\n\ncomments: true \n\ntags: \n - Blob\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-10\n \n---\n\n# :如果是插入某个文件可以用mysql的Blob类型\n\n#### *Blob类型,二进制大对象,用来存储二进制文件(图片等)*\n下面是简单的示例\n\n\n\n```java\npackage com.jdbc;\n\nimport org.junit.Test;\n\nimport java.io.*;\nimport java.sql.*;\nimport java.util.Properties;\n\n/**\n * Created by zj on 2017/2/10.\n */\npublic class insertBlob {\n @Test\n public void test() {\n InputStream inputStream = null;\n /*\n\t\t插入BLOB类型数据 (必须使用PreparedStatement)\n */\n\t\ttry {\n inputStream = new FileInputStream(\"E:/app/Project/Test_Demo/src/java.jpg\");\n insert(\"insert into teacher(name, birth, picture) values (?, ?, ?)\",\n \"fucc\", new Date(System.currentTimeMillis()), inputStream);\n } catch (FileNotFoundException e) {\n e.printStackTrace();\n } finally {\n if (inputStream != null) {\n try {\n inputStream.close();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n }\n\n /*\n 查询并展示Blob类型数据\n */\n query(\"SELECT id id, name name, birth birth, picture picture FROM teacher WHERE id = ?\",\n 442);\n }\n\n\t//插入方法(其实删除,修改也可以使用)\n\t//插入Blob类型(mysql)需要传入InputStream类型\n public int insert(String sql, Object... args) {\n Connection connection = null;\n PreparedStatement state = null;\n int result = 0;\n\n try {\n connection = getConnection();\n state = connection.prepareStatement(sql);\n for (int i = 0; i < args.length; i++) {\n //专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);\n state.setObject(i + 1, args[i]); //遍历设置占位符的值\n }\n result = state.executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close(connection, state, null);\n }\n\n return result;\n }\n\n\t//读取 读取Blob类型时, 需要用InputStream类型接收在用OutputStream读取到文件中方可访问\n public void query(String sql, Object... args) {\n Connection connection = null;\n PreparedStatement state = null;\n ResultSet resultSet = null;\n InputStream inputStream = null;\n OutputStream outputStream = null;\n\n try {\n connection = getConnection();\n state = connection.prepareStatement(sql);\n for (int i = 0; i < args.length; i++) {\n //专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);\n state.setObject(i + 1, args[i]); //遍历设置占位符的值\n }\n resultSet = state.executeQuery();\n while (resultSet.next()) {\n int id = resultSet.getInt(1);\n String name = resultSet.getString(2);\n Date date = resultSet.getDate(3);\n Blob picture = resultSet.getBlob(4);\n System.out.println(id + \" \" + name + \" \" + date + \" \" + picture);\n\n\t\t\t\t//读取文件(Blob->InputStream->OutputStream->文件)\n\t\t\t\tinputStream = picture.getBinaryStream();\n outputStream = new FileOutputStream(\"pic.jpg\");\n byte[] bytes = new byte[50];\n int len;\n while ((len = inputStream.read(bytes)) != -1) {\n outputStream.write(bytes, 0, len);\n }\n }\n } catch (SQLException | IOException e) {\n e.printStackTrace();\n } finally {\n if(outputStream != null){\n try {\n outputStream.close();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n if(inputStream != null){\n try {\n inputStream.close();\n } catch (IOException e) {\n e.printStackTrace();\n }\n }\n\n close(connection, state, resultSet);\n }\n }\n\n private Connection getConnection() {\n Connection connection = null;\n try {\n Properties properties = new Properties();\n //读取文件中的数据库配置信息赋值给各个变量\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n\n Class.forName(dirverClass);\n connection = DriverManager.getConnection(url, user, password);\n } catch (SQLException | IOException | ReflectiveOperationException e) {\n e.printStackTrace();\n }\n\n return connection;\n }\n\n private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (statement != null) {\n try {\n statement.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n}\n\n```","slug":"2017_jdbc_file","published":1,"updated":"2018-01-13T02:29:22.249Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwf003824ujt1xhlte5","content":"

:如果是插入某个文件可以用mysql的Blob类型

Blob类型,二进制大对象,用来存储二进制文件(图片等)

下面是简单的示例

\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.jdbc;

import org.junit.Test;

import java.io.*;
import java.sql.*;
import java.util.Properties;

/**
* Created by zj on 2017/2/10.
*/
public class insertBlob {
@Test
public void test() {
InputStream inputStream = null;
/*
\t\t插入BLOB类型数据 (必须使用PreparedStatement)
*/
\t\ttry {
inputStream = new FileInputStream(\"E:/app/Project/Test_Demo/src/java.jpg\");
insert(\"insert into teacher(name, birth, picture) values (?, ?, ?)\",
\"fucc\", new Date(System.currentTimeMillis()), inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/*
查询并展示Blob类型数据
*/
query(\"SELECT id id, name name, birth birth, picture picture FROM teacher WHERE id = ?\",
442);
}

\t//插入方法(其实删除,修改也可以使用)
\t//插入Blob类型(mysql)需要传入InputStream类型
public int insert(String sql, Object... args) {
Connection connection = null;
PreparedStatement state = null;
int result = 0;

try {
connection = getConnection();
state = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
//专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);
state.setObject(i + 1, args[i]); //遍历设置占位符的值
}
result = state.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(connection, state, null);
}

return result;
}

\t//读取 读取Blob类型时, 需要用InputStream类型接收在用OutputStream读取到文件中方可访问
public void query(String sql, Object... args) {
Connection connection = null;
PreparedStatement state = null;
ResultSet resultSet = null;
InputStream inputStream = null;
OutputStream outputStream = null;

try {
connection = getConnection();
state = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
//专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);
state.setObject(i + 1, args[i]); //遍历设置占位符的值
}
resultSet = state.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
Date date = resultSet.getDate(3);
Blob picture = resultSet.getBlob(4);
System.out.println(id + \" \" + name + \" \" + date + \" \" + picture);

\t\t\t\t//读取文件(Blob->InputStream->OutputStream->文件)
\t\t\t\tinputStream = picture.getBinaryStream();
outputStream = new FileOutputStream(\"pic.jpg\");
byte[] bytes = new byte[50];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
}
} catch (SQLException | IOException e) {
e.printStackTrace();
} finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}

close(connection, state, resultSet);
}
}

private Connection getConnection() {
Connection connection = null;
try {
Properties properties = new Properties();
//读取文件中的数据库配置信息赋值给各个变量
properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));
String dirverClass = properties.getProperty(\"driver\");
String url = properties.getProperty(\"jdbc_url\");
String user = properties.getProperty(\"user\");
String password = properties.getProperty(\"password\");

Class.forName(dirverClass);
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException | IOException | ReflectiveOperationException e) {
e.printStackTrace();
}

return connection;
}

private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
","site":{"data":{}},"excerpt":"

:如果是插入某个文件可以用mysql的Blob类型

Blob类型,二进制大对象,用来存储二进制文件(图片等)

下面是简单的示例

","more":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package com.jdbc;

import org.junit.Test;

import java.io.*;
import java.sql.*;
import java.util.Properties;

/**
* Created by zj on 2017/2/10.
*/
public class insertBlob {
@Test
public void test() {
InputStream inputStream = null;
/*
\t\t插入BLOB类型数据 (必须使用PreparedStatement)
*/
\t\ttry {
inputStream = new FileInputStream(\"E:/app/Project/Test_Demo/src/java.jpg\");
insert(\"insert into teacher(name, birth, picture) values (?, ?, ?)\",
\"fucc\", new Date(System.currentTimeMillis()), inputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/*
查询并展示Blob类型数据
*/
query(\"SELECT id id, name name, birth birth, picture picture FROM teacher WHERE id = ?\",
442);
}

\t//插入方法(其实删除,修改也可以使用)
\t//插入Blob类型(mysql)需要传入InputStream类型
public int insert(String sql, Object... args) {
Connection connection = null;
PreparedStatement state = null;
int result = 0;

try {
connection = getConnection();
state = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
//专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);
state.setObject(i + 1, args[i]); //遍历设置占位符的值
}
result = state.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(connection, state, null);
}

return result;
}

\t//读取 读取Blob类型时, 需要用InputStream类型接收在用OutputStream读取到文件中方可访问
public void query(String sql, Object... args) {
Connection connection = null;
PreparedStatement state = null;
ResultSet resultSet = null;
InputStream inputStream = null;
OutputStream outputStream = null;

try {
connection = getConnection();
state = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
//专门用来设置blob类型的方法是state.setBlob(InputStream inputstream);
state.setObject(i + 1, args[i]); //遍历设置占位符的值
}
resultSet = state.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
Date date = resultSet.getDate(3);
Blob picture = resultSet.getBlob(4);
System.out.println(id + \" \" + name + \" \" + date + \" \" + picture);

\t\t\t\t//读取文件(Blob->InputStream->OutputStream->文件)
\t\t\t\tinputStream = picture.getBinaryStream();
outputStream = new FileOutputStream(\"pic.jpg\");
byte[] bytes = new byte[50];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
}
} catch (SQLException | IOException e) {
e.printStackTrace();
} finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}

close(connection, state, resultSet);
}
}

private Connection getConnection() {
Connection connection = null;
try {
Properties properties = new Properties();
//读取文件中的数据库配置信息赋值给各个变量
properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));
String dirverClass = properties.getProperty(\"driver\");
String url = properties.getProperty(\"jdbc_url\");
String user = properties.getProperty(\"user\");
String password = properties.getProperty(\"password\");

Class.forName(dirverClass);
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException | IOException | ReflectiveOperationException e) {
e.printStackTrace();
}

return connection;
}

private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
"},{"title":"重新学习JDBC之获取连接","comments":1,"description":null,"date":"2017-02-06T16:00:00.000Z","_content":"\n\n\n```java\npackage com.jdbc;\n\nimport org.apache.commons.collections.map.HashedMap;\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.sql.*;\nimport java.util.*;\n\n/**\n * Created by zj2626 on 17-1-19.\n */\npublic class testUtils {\n private static Connection connection = null;\n private static PreparedStatement state = null;\n private static ResultSet resultSet = null;\n\n @Test\n public void test() {\n Integer id = 123;\n String name = \"name\";\n Integer age = 20;\n\n List list = new ArrayList<>();\n list.add(id);\n list.add(name);\n list.add(age);\n\n //操作数据库版本一\n String sql = \"insert into student(id, name, age) values(\" + id + \", '\" + name + \"', \" + age + \")\";\n editWayOne(sql);\n\n //操作数据库版本二(可以防止sql注入)\n String sql2 = \"insert into student(id, name, age) values(?, ?, ?)\";\n //editWayTwo(sql2, list);\n\n //查询\n String sql3 = \"select id, name, age from student where id = ?\";\n query(sql3, id);\n }\n\n /**\n * low版本的增删改方法 (增删改都可用)\n *\n * @param sql 普通sql语句\n * @return 执行成功的记录的条数\n */\n private int editWayOne(String sql) {\n int num = 0;\n try {\n getConnection(sql);\n num = state.executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close();\n }\n return num;\n }\n\n /**\n * @param sql 带占位符的sql语句\n * @param list 占位符要传入的值\n * @return 执行成功的记录的条数\n */\n private int editWayTwo(String sql, List list) {\n int num = 0;\n try {\n getConnection(sql);\n for (int i = 0; i < list.size(); i++) {\n //这里统一用setObject了 其实应该用相对应类型的方法(setString,setInt,setDate...)\n state.setObject(i + 1, list.get(i));\n }\n num = state.executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close();\n }\n return num;\n }\n\n /**\n * 查询\n *\n * @param sql 查询语句\n * @return 返回查询到的所有数据集合\n */\n private List> query(String sql, Integer id) {\n List> result = new ArrayList<>();\n try {\n getConnection(sql);\n state.setObject(1, id);\n //结果集(ResultSet)是数据中查询结果返回的一种对象 该对象存储了查询出的数据需要遍历取出\n resultSet = state.executeQuery();\n\n while (resultSet.next()) {//查看有没有下一条数据\n Map map = new HashMap();//存放每条数据的每个查询到的字段\n map.put(\"id\", resultSet.getInt(1));//遍历也可以通过resultSet.getInt(\"id\")更明确\n map.put(\"name\", resultSet.getString(2));\n map.put(\"age\", resultSet.getInt(3));\n\n result.add(map);\n }\n\n System.out.println(\"数据条数\" + result.size());\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close();\n }\n\n return result;\n }\n\n private void getConnection(String sql) {\n Properties properties = new Properties();\n try {\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n try {\n Class.forName(dirverClass);\n connection = DriverManager.getConnection(url, user, password);\n state = connection.prepareStatement(sql);\n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n\n private void close() {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (state != null) {\n try {\n state.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n}\n\n```","source":"_posts/2017_jdbc_curd.md","raw":"---\ntitle: 重新学习JDBC之获取连接\n\ncomments: true \n\ntags: \n - CRUD\n - 工具类\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-07\n \n---\n\n\n\n```java\npackage com.jdbc;\n\nimport org.apache.commons.collections.map.HashedMap;\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.sql.*;\nimport java.util.*;\n\n/**\n * Created by zj2626 on 17-1-19.\n */\npublic class testUtils {\n private static Connection connection = null;\n private static PreparedStatement state = null;\n private static ResultSet resultSet = null;\n\n @Test\n public void test() {\n Integer id = 123;\n String name = \"name\";\n Integer age = 20;\n\n List list = new ArrayList<>();\n list.add(id);\n list.add(name);\n list.add(age);\n\n //操作数据库版本一\n String sql = \"insert into student(id, name, age) values(\" + id + \", '\" + name + \"', \" + age + \")\";\n editWayOne(sql);\n\n //操作数据库版本二(可以防止sql注入)\n String sql2 = \"insert into student(id, name, age) values(?, ?, ?)\";\n //editWayTwo(sql2, list);\n\n //查询\n String sql3 = \"select id, name, age from student where id = ?\";\n query(sql3, id);\n }\n\n /**\n * low版本的增删改方法 (增删改都可用)\n *\n * @param sql 普通sql语句\n * @return 执行成功的记录的条数\n */\n private int editWayOne(String sql) {\n int num = 0;\n try {\n getConnection(sql);\n num = state.executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close();\n }\n return num;\n }\n\n /**\n * @param sql 带占位符的sql语句\n * @param list 占位符要传入的值\n * @return 执行成功的记录的条数\n */\n private int editWayTwo(String sql, List list) {\n int num = 0;\n try {\n getConnection(sql);\n for (int i = 0; i < list.size(); i++) {\n //这里统一用setObject了 其实应该用相对应类型的方法(setString,setInt,setDate...)\n state.setObject(i + 1, list.get(i));\n }\n num = state.executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close();\n }\n return num;\n }\n\n /**\n * 查询\n *\n * @param sql 查询语句\n * @return 返回查询到的所有数据集合\n */\n private List> query(String sql, Integer id) {\n List> result = new ArrayList<>();\n try {\n getConnection(sql);\n state.setObject(1, id);\n //结果集(ResultSet)是数据中查询结果返回的一种对象 该对象存储了查询出的数据需要遍历取出\n resultSet = state.executeQuery();\n\n while (resultSet.next()) {//查看有没有下一条数据\n Map map = new HashMap();//存放每条数据的每个查询到的字段\n map.put(\"id\", resultSet.getInt(1));//遍历也可以通过resultSet.getInt(\"id\")更明确\n map.put(\"name\", resultSet.getString(2));\n map.put(\"age\", resultSet.getInt(3));\n\n result.add(map);\n }\n\n System.out.println(\"数据条数\" + result.size());\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n close();\n }\n\n return result;\n }\n\n private void getConnection(String sql) {\n Properties properties = new Properties();\n try {\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n try {\n Class.forName(dirverClass);\n connection = DriverManager.getConnection(url, user, password);\n state = connection.prepareStatement(sql);\n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n\n private void close() {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (state != null) {\n try {\n state.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n}\n\n```","slug":"2017_jdbc_curd","published":1,"updated":"2018-01-13T02:29:22.247Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwh003b24uj7i4y6a9l","content":"\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package com.jdbc;

import org.apache.commons.collections.map.HashedMap;
import org.junit.Test;

import java.io.IOException;
import java.sql.*;
import java.util.*;

/**
* Created by zj2626 on 17-1-19.
*/
public class testUtils {
private static Connection connection = null;
private static PreparedStatement state = null;
private static ResultSet resultSet = null;

@Test
public void test() {
Integer id = 123;
String name = \"name\";
Integer age = 20;

List<Object> list = new ArrayList<>();
list.add(id);
list.add(name);
list.add(age);

//操作数据库版本一
String sql = \"insert into student(id, name, age) values(\" + id + \", '\" + name + \"', \" + age + \")\";
editWayOne(sql);

//操作数据库版本二(可以防止sql注入)
String sql2 = \"insert into student(id, name, age) values(?, ?, ?)\";
//editWayTwo(sql2, list);

//查询
String sql3 = \"select id, name, age from student where id = ?\";
query(sql3, id);
}

/**
* low版本的增删改方法 (增删改都可用)
*
* @param sql 普通sql语句
* @return 执行成功的记录的条数
*/
private int editWayOne(String sql) {
int num = 0;
try {
getConnection(sql);
num = state.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close();
}
return num;
}

/**
* @param sql 带占位符的sql语句
* @param list 占位符要传入的值
* @return 执行成功的记录的条数
*/
private int editWayTwo(String sql, List<Object> list) {
int num = 0;
try {
getConnection(sql);
for (int i = 0; i < list.size(); i++) {
//这里统一用setObject了 其实应该用相对应类型的方法(setString,setInt,setDate...)
state.setObject(i + 1, list.get(i));
}
num = state.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close();
}
return num;
}

/**
* 查询
*
* @param sql 查询语句
* @return 返回查询到的所有数据集合
*/
private List<Map<String, Object>> query(String sql, Integer id) {
List<Map<String, Object>> result = new ArrayList<>();
try {
getConnection(sql);
state.setObject(1, id);
//结果集(ResultSet)是数据中查询结果返回的一种对象 该对象存储了查询出的数据需要遍历取出
resultSet = state.executeQuery();

while (resultSet.next()) {//查看有没有下一条数据
Map<String, Object> map = new HashMap();//存放每条数据的每个查询到的字段
map.put(\"id\", resultSet.getInt(1));//遍历也可以通过resultSet.getInt(\"id\")更明确
map.put(\"name\", resultSet.getString(2));
map.put(\"age\", resultSet.getInt(3));

result.add(map);
}

System.out.println(\"数据条数\" + result.size());
} catch (SQLException e) {
e.printStackTrace();
} finally {
close();
}

return result;
}

private void getConnection(String sql) {
Properties properties = new Properties();
try {
properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));
} catch (IOException e) {
e.printStackTrace();
}

String dirverClass = properties.getProperty(\"driver\");
String url = properties.getProperty(\"jdbc_url\");
String user = properties.getProperty(\"user\");
String password = properties.getProperty(\"password\");
try {
Class.forName(dirverClass);
connection = DriverManager.getConnection(url, user, password);
state = connection.prepareStatement(sql);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
}

private void close() {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (state != null) {
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
","site":{"data":{}},"excerpt":"","more":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package com.jdbc;

import org.apache.commons.collections.map.HashedMap;
import org.junit.Test;

import java.io.IOException;
import java.sql.*;
import java.util.*;

/**
* Created by zj2626 on 17-1-19.
*/
public class testUtils {
private static Connection connection = null;
private static PreparedStatement state = null;
private static ResultSet resultSet = null;

@Test
public void test() {
Integer id = 123;
String name = \"name\";
Integer age = 20;

List<Object> list = new ArrayList<>();
list.add(id);
list.add(name);
list.add(age);

//操作数据库版本一
String sql = \"insert into student(id, name, age) values(\" + id + \", '\" + name + \"', \" + age + \")\";
editWayOne(sql);

//操作数据库版本二(可以防止sql注入)
String sql2 = \"insert into student(id, name, age) values(?, ?, ?)\";
//editWayTwo(sql2, list);

//查询
String sql3 = \"select id, name, age from student where id = ?\";
query(sql3, id);
}

/**
* low版本的增删改方法 (增删改都可用)
*
* @param sql 普通sql语句
* @return 执行成功的记录的条数
*/
private int editWayOne(String sql) {
int num = 0;
try {
getConnection(sql);
num = state.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close();
}
return num;
}

/**
* @param sql 带占位符的sql语句
* @param list 占位符要传入的值
* @return 执行成功的记录的条数
*/
private int editWayTwo(String sql, List<Object> list) {
int num = 0;
try {
getConnection(sql);
for (int i = 0; i < list.size(); i++) {
//这里统一用setObject了 其实应该用相对应类型的方法(setString,setInt,setDate...)
state.setObject(i + 1, list.get(i));
}
num = state.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close();
}
return num;
}

/**
* 查询
*
* @param sql 查询语句
* @return 返回查询到的所有数据集合
*/
private List<Map<String, Object>> query(String sql, Integer id) {
List<Map<String, Object>> result = new ArrayList<>();
try {
getConnection(sql);
state.setObject(1, id);
//结果集(ResultSet)是数据中查询结果返回的一种对象 该对象存储了查询出的数据需要遍历取出
resultSet = state.executeQuery();

while (resultSet.next()) {//查看有没有下一条数据
Map<String, Object> map = new HashMap();//存放每条数据的每个查询到的字段
map.put(\"id\", resultSet.getInt(1));//遍历也可以通过resultSet.getInt(\"id\")更明确
map.put(\"name\", resultSet.getString(2));
map.put(\"age\", resultSet.getInt(3));

result.add(map);
}

System.out.println(\"数据条数\" + result.size());
} catch (SQLException e) {
e.printStackTrace();
} finally {
close();
}

return result;
}

private void getConnection(String sql) {
Properties properties = new Properties();
try {
properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));
} catch (IOException e) {
e.printStackTrace();
}

String dirverClass = properties.getProperty(\"driver\");
String url = properties.getProperty(\"jdbc_url\");
String user = properties.getProperty(\"user\");
String password = properties.getProperty(\"password\");
try {
Class.forName(dirverClass);
connection = DriverManager.getConnection(url, user, password);
state = connection.prepareStatement(sql);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}
}

private void close() {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (state != null) {
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
"},{"title":"重新学习JDBC之获取statement","comments":1,"description":null,"date":"2017-02-05T16:00:00.000Z","_content":"\n# **Statement类 **\n获取connection(见 http://www.zj2626.github.io/index.php/2017/02/06/cxxxjdbc/) 之后, 需要获得sql语句并发送然后执行sql语句,所以有了本章\n\n\n\n>直接看代码\n\n private Connection getConnection(String sql) {\n Properties properties = new Properties();\n try {\n //获取配置文件中的数据库信息\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n \n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n Connection connection = null;\n Statement state = null;\n try {\n //加载数据库驱动 \n Class.forName(dirverClass);\n //获取数据库连接\n connection = DriverManager.getConnection(url, user, password);\n \n //获取Statment对象\n state = connection.createStatement();\n //执行sql语句\n state.execute(sql);\n \n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n \n if (state != null) {//关闭statment对象,释放资源\n try {\n state.close();\n } catch (SQLException e) {\n System.out.println(e);\n }\n }\n if (null != connection) {//关闭connction对象,释放资源\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n\n\n---\n\n# **升级**\nStatement对象创建之后,没执行一次都会重新编译一次sql语句(sql语句是执行时候参数嘛),这很不好\n所以我们用其子类PreparedStatement\n\n1. 创建时的区别: \n\t2. Statement statement = conn.createStatement();\n\t3. PreparedStatement preStatement = conn.prepareStatement(sql); \n4. 执行的时候: \n\t5. ResultSet rSet = statement.executeQuery(sql);\n\t6. ResultSet pSet = preStatement.executeQuery();\n看出,PreparedStatement有预编译的过程,已经绑定sql,之后无论执行多少遍,都不会再去进行编译,效率高\n\n\n private static Connection connection = null;\n private static PreparedStatement prep = null;\n \n private void getConnection(String sql) {\n Properties properties = new Properties();\n try {\n //获取配置文件中的数据库信息\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n \n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n try {\n //加载数据库驱动\n Class.forName(dirverClass);\n //获取数据库连接\n connection = DriverManager.getConnection(url, user, password);\n prep = connection.prepareStatement(sql);\n prep.execute();//这里是更新操作的事例 如果是查询等其他则调用方法有不同\n \n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n\n\n//执行数据库操作之后必须关闭各个对象(按顺序)\n\n\n public void close(Statement state, Connection connection) {\n if (state != null) {\n try {\n state.close();\n } catch (SQLException e) {\n System.out.println(e);\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n","source":"_posts/2017_jdbc_getStatement.md","raw":"---\ntitle: 重新学习JDBC之获取statement\n\ncomments: true \n\ntags: \n - JDBC\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-06\n \n---\n\n# **Statement类 **\n获取connection(见 http://www.zj2626.github.io/index.php/2017/02/06/cxxxjdbc/) 之后, 需要获得sql语句并发送然后执行sql语句,所以有了本章\n\n\n\n>直接看代码\n\n private Connection getConnection(String sql) {\n Properties properties = new Properties();\n try {\n //获取配置文件中的数据库信息\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n \n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n Connection connection = null;\n Statement state = null;\n try {\n //加载数据库驱动 \n Class.forName(dirverClass);\n //获取数据库连接\n connection = DriverManager.getConnection(url, user, password);\n \n //获取Statment对象\n state = connection.createStatement();\n //执行sql语句\n state.execute(sql);\n \n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n \n if (state != null) {//关闭statment对象,释放资源\n try {\n state.close();\n } catch (SQLException e) {\n System.out.println(e);\n }\n }\n if (null != connection) {//关闭connction对象,释放资源\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n\n\n---\n\n# **升级**\nStatement对象创建之后,没执行一次都会重新编译一次sql语句(sql语句是执行时候参数嘛),这很不好\n所以我们用其子类PreparedStatement\n\n1. 创建时的区别: \n\t2. Statement statement = conn.createStatement();\n\t3. PreparedStatement preStatement = conn.prepareStatement(sql); \n4. 执行的时候: \n\t5. ResultSet rSet = statement.executeQuery(sql);\n\t6. ResultSet pSet = preStatement.executeQuery();\n看出,PreparedStatement有预编译的过程,已经绑定sql,之后无论执行多少遍,都不会再去进行编译,效率高\n\n\n private static Connection connection = null;\n private static PreparedStatement prep = null;\n \n private void getConnection(String sql) {\n Properties properties = new Properties();\n try {\n //获取配置文件中的数据库信息\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n \n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n try {\n //加载数据库驱动\n Class.forName(dirverClass);\n //获取数据库连接\n connection = DriverManager.getConnection(url, user, password);\n prep = connection.prepareStatement(sql);\n prep.execute();//这里是更新操作的事例 如果是查询等其他则调用方法有不同\n \n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n\n\n//执行数据库操作之后必须关闭各个对象(按顺序)\n\n\n public void close(Statement state, Connection connection) {\n if (state != null) {\n try {\n state.close();\n } catch (SQLException e) {\n System.out.println(e);\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n","slug":"2017_jdbc_getStatement","published":1,"updated":"2021-03-10T13:50:15.263Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwj003g24ujgewd1hwa","content":"

Statement类

获取connection(见 http://www.zj2626.github.io/index.php/2017/02/06/cxxxjdbc/) 之后, 需要获得sql语句并发送然后执行sql语句,所以有了本章

\n\n
\n

直接看代码

\n
\n
private Connection getConnection(String sql) {\n        Properties properties = new Properties();\n        try {\n                //获取配置文件中的数据库信息\n                properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n        } catch (IOException e) {\n                e.printStackTrace();\n        }\n\n        String dirverClass = properties.getProperty("driver");\n        String url = properties.getProperty("jdbc_url");\n        String user = properties.getProperty("user");\n        String password = properties.getProperty("password");\n\n        Connection connection = null;\n        Statement state = null;\n        try {\n            //加载数据库驱动 \n            Class.forName(dirverClass);\n            //获取数据库连接\n            connection = DriverManager.getConnection(url, user, password);\n\n            //获取Statment对象\n            state = connection.createStatement();\n            //执行sql语句\n            state.execute(sql);\n\n        } catch (SQLException | ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n\n        if (state != null) {//关闭statment对象,释放资源\n            try {\n                state.close();\n            } catch (SQLException e) {\n                System.out.println(e);\n            }\n        }\n        if (null != connection) {//关闭connction对象,释放资源\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n}\n

\n

升级

Statement对象创建之后,没执行一次都会重新编译一次sql语句(sql语句是执行时候参数嘛),这很不好
所以我们用其子类PreparedStatement

\n
    \n
  1. 创建时的区别:
      \n
    1. Statement statement = conn.createStatement();
    2. \n
    3. PreparedStatement preStatement = conn.prepareStatement(sql);
    4. \n
    \n
  2. \n
  3. 执行的时候:
      \n
    1. ResultSet rSet = statement.executeQuery(sql);
    2. \n
    3. ResultSet pSet = preStatement.executeQuery();
      看出,PreparedStatement有预编译的过程,已经绑定sql,之后无论执行多少遍,都不会再去进行编译,效率高
    4. \n
    \n
  4. \n
\n
private static Connection connection = null;\nprivate static PreparedStatement prep = null;\n\nprivate void getConnection(String sql) {\n    Properties properties = new Properties();\n    try {\n        //获取配置文件中的数据库信息\n        properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n    } catch (IOException e) {\n        e.printStackTrace();\n    }\n\n    String dirverClass = properties.getProperty("driver");\n    String url = properties.getProperty("jdbc_url");\n    String user = properties.getProperty("user");\n    String password = properties.getProperty("password");\n\n    try {\n        //加载数据库驱动\n        Class.forName(dirverClass);\n        //获取数据库连接\n        connection = DriverManager.getConnection(url, user, password);\n        prep = connection.prepareStatement(sql);\n        prep.execute();//这里是更新操作的事例 如果是查询等其他则调用方法有不同\n\n    } catch (SQLException | ClassNotFoundException e) {\n        e.printStackTrace();\n    }\n}\n

//执行数据库操作之后必须关闭各个对象(按顺序)

\n
public void close(Statement state, Connection connection) {\n    if (state != null) {\n        try {\n            state.close();\n        } catch (SQLException e) {\n            System.out.println(e);\n        }\n    }\n    if (null != connection) {\n        try {\n            connection.close();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n    }\n}\n
","site":{"data":{}},"excerpt":"

Statement类

获取connection(见 http://www.zj2626.github.io/index.php/2017/02/06/cxxxjdbc/) 之后, 需要获得sql语句并发送然后执行sql语句,所以有了本章

","more":"
\n

直接看代码

\n
\n
private Connection getConnection(String sql) {\n        Properties properties = new Properties();\n        try {\n                //获取配置文件中的数据库信息\n                properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n        } catch (IOException e) {\n                e.printStackTrace();\n        }\n\n        String dirverClass = properties.getProperty("driver");\n        String url = properties.getProperty("jdbc_url");\n        String user = properties.getProperty("user");\n        String password = properties.getProperty("password");\n\n        Connection connection = null;\n        Statement state = null;\n        try {\n            //加载数据库驱动 \n            Class.forName(dirverClass);\n            //获取数据库连接\n            connection = DriverManager.getConnection(url, user, password);\n\n            //获取Statment对象\n            state = connection.createStatement();\n            //执行sql语句\n            state.execute(sql);\n\n        } catch (SQLException | ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n\n        if (state != null) {//关闭statment对象,释放资源\n            try {\n                state.close();\n            } catch (SQLException e) {\n                System.out.println(e);\n            }\n        }\n        if (null != connection) {//关闭connction对象,释放资源\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n}\n

\n

升级

Statement对象创建之后,没执行一次都会重新编译一次sql语句(sql语句是执行时候参数嘛),这很不好
所以我们用其子类PreparedStatement

\n
    \n
  1. 创建时的区别:
      \n
    1. Statement statement = conn.createStatement();
    2. \n
    3. PreparedStatement preStatement = conn.prepareStatement(sql);
    4. \n
    \n
  2. \n
  3. 执行的时候:
      \n
    1. ResultSet rSet = statement.executeQuery(sql);
    2. \n
    3. ResultSet pSet = preStatement.executeQuery();
      看出,PreparedStatement有预编译的过程,已经绑定sql,之后无论执行多少遍,都不会再去进行编译,效率高
    4. \n
    \n
  4. \n
\n
private static Connection connection = null;\nprivate static PreparedStatement prep = null;\n\nprivate void getConnection(String sql) {\n    Properties properties = new Properties();\n    try {\n        //获取配置文件中的数据库信息\n        properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n    } catch (IOException e) {\n        e.printStackTrace();\n    }\n\n    String dirverClass = properties.getProperty("driver");\n    String url = properties.getProperty("jdbc_url");\n    String user = properties.getProperty("user");\n    String password = properties.getProperty("password");\n\n    try {\n        //加载数据库驱动\n        Class.forName(dirverClass);\n        //获取数据库连接\n        connection = DriverManager.getConnection(url, user, password);\n        prep = connection.prepareStatement(sql);\n        prep.execute();//这里是更新操作的事例 如果是查询等其他则调用方法有不同\n\n    } catch (SQLException | ClassNotFoundException e) {\n        e.printStackTrace();\n    }\n}\n

//执行数据库操作之后必须关闭各个对象(按顺序)

\n
public void close(Statement state, Connection connection) {\n    if (state != null) {\n        try {\n            state.close();\n        } catch (SQLException e) {\n            System.out.println(e);\n        }\n    }\n    if (null != connection) {\n        try {\n            connection.close();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n    }\n}\n
"},{"title":"Jdbc实现简单的事务处理","comments":1,"description":null,"date":"2017-02-09T16:00:00.000Z","_content":"\n\n\n package com.jdbc;\n \n import org.junit.Test;\n \n import java.io.IOException;\n import java.sql.*;\n import java.util.Properties;\n \n /**\n * Created by zj on 2017/2/10.\n */\n public class ACID {\n /*\n JDBC实现数据库事务操作\n 1.原子性(事务不可分割)\n 2.一致性(一致性状态-->另一个一致性状态)\n 3.隔离性(类似于加锁, 某一刻一个数据只能被一个事务操作)\n 4.持久性(事务提交,数据库的改变就是永久的)\n \n 要实现 需要多个操作使用同一个连接(Connection)\n */\n @Test\n public void test() {\n Connection connection = null;\n try {\n connection = getConnection(\"update student set f_age = f_age + \" + 2 + \" where f_id = \" + 1, null);\n connection = getConnection(\"update student set f_age = f_age * \" + 10 + \" where f_id = \" + 1, connection);\n connection.commit();//如果前面的操作都成功,手动提交事务\n } catch (SQLException e) {\n if (connection != null) {\n try {\n connection.rollback();//有异常就回滚事务\n } catch (SQLException e1) {\n e1.printStackTrace();\n }\n }\n } finally {\n close(connection, null, null);//关闭\n }\n }\n \n private Connection getConnection(String sql, Connection connection) throws SQLException {\n int result;\n PreparedStatement preparedStatement = null;\n try {\n Properties properties = new Properties();\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n Class.forName(dirverClass);\n if (connection == null) {\n connection = DriverManager.getConnection(url, user, password);\n //取消自动提交(Connection的默认提交行为)\n connection.setAutoCommit(false);\n }\n preparedStatement = connection.prepareStatement(sql);\n result = preparedStatement.executeUpdate();\n System.out.println(result);\n } catch (IOException e) {\n throw new RuntimeException();\n } catch (ClassNotFoundException e) {\n e.printStackTrace();\n } finally {\n close(null, preparedStatement, null);\n }\n \n return connection;\n }\n \n private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (statement != null) {\n try {\n statement.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n \n }\n","source":"_posts/2017_jdbc_transaction.md","raw":"---\ntitle: Jdbc实现简单的事务处理\n\ncomments: true \n\ntags: \n - 事务\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-10\n \n---\n\n\n\n package com.jdbc;\n \n import org.junit.Test;\n \n import java.io.IOException;\n import java.sql.*;\n import java.util.Properties;\n \n /**\n * Created by zj on 2017/2/10.\n */\n public class ACID {\n /*\n JDBC实现数据库事务操作\n 1.原子性(事务不可分割)\n 2.一致性(一致性状态-->另一个一致性状态)\n 3.隔离性(类似于加锁, 某一刻一个数据只能被一个事务操作)\n 4.持久性(事务提交,数据库的改变就是永久的)\n \n 要实现 需要多个操作使用同一个连接(Connection)\n */\n @Test\n public void test() {\n Connection connection = null;\n try {\n connection = getConnection(\"update student set f_age = f_age + \" + 2 + \" where f_id = \" + 1, null);\n connection = getConnection(\"update student set f_age = f_age * \" + 10 + \" where f_id = \" + 1, connection);\n connection.commit();//如果前面的操作都成功,手动提交事务\n } catch (SQLException e) {\n if (connection != null) {\n try {\n connection.rollback();//有异常就回滚事务\n } catch (SQLException e1) {\n e1.printStackTrace();\n }\n }\n } finally {\n close(connection, null, null);//关闭\n }\n }\n \n private Connection getConnection(String sql, Connection connection) throws SQLException {\n int result;\n PreparedStatement preparedStatement = null;\n try {\n Properties properties = new Properties();\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n Class.forName(dirverClass);\n if (connection == null) {\n connection = DriverManager.getConnection(url, user, password);\n //取消自动提交(Connection的默认提交行为)\n connection.setAutoCommit(false);\n }\n preparedStatement = connection.prepareStatement(sql);\n result = preparedStatement.executeUpdate();\n System.out.println(result);\n } catch (IOException e) {\n throw new RuntimeException();\n } catch (ClassNotFoundException e) {\n e.printStackTrace();\n } finally {\n close(null, preparedStatement, null);\n }\n \n return connection;\n }\n \n private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (statement != null) {\n try {\n statement.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n \n }\n","slug":"2017_jdbc_transaction","published":1,"updated":"2018-01-13T02:29:22.255Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwl003j24ujng4lctg8","content":"\n
package com.jdbc;\n\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.sql.*;\nimport java.util.Properties;\n\n/**\n * Created by zj on 2017/2/10.\n */\npublic class ACID {\n    /*\n        JDBC实现数据库事务操作\n        1.原子性(事务不可分割)\n        2.一致性(一致性状态-->另一个一致性状态)\n        3.隔离性(类似于加锁, 某一刻一个数据只能被一个事务操作)\n        4.持久性(事务提交,数据库的改变就是永久的)\n\n        要实现 需要多个操作使用同一个连接(Connection)\n     */\n    @Test\n    public void test() {\n        Connection connection = null;\n        try {\n            connection = getConnection("update student set f_age = f_age + " + 2 + " where f_id = " + 1, null);\n            connection = getConnection("update student set f_age = f_age * " + 10 + " where f_id = " + 1, connection);\n            connection.commit();//如果前面的操作都成功,手动提交事务\n        } catch (SQLException e) {\n            if (connection != null) {\n                try {\n                    connection.rollback();//有异常就回滚事务\n                } catch (SQLException e1) {\n                    e1.printStackTrace();\n                }\n            }\n        } finally {\n            close(connection, null, null);//关闭\n        }\n    }\n\n    private Connection getConnection(String sql, Connection connection) throws SQLException {\n        int result;\n        PreparedStatement preparedStatement = null;\n        try {\n            Properties properties = new Properties();\n            properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n            String dirverClass = properties.getProperty("driver");\n            String url = properties.getProperty("jdbc_url");\n            String user = properties.getProperty("user");\n            String password = properties.getProperty("password");\n\n            Class.forName(dirverClass);\n            if (connection == null) {\n                connection = DriverManager.getConnection(url, user, password);\n                //取消自动提交(Connection的默认提交行为)\n                connection.setAutoCommit(false);\n            }\n            preparedStatement = connection.prepareStatement(sql);\n            result = preparedStatement.executeUpdate();\n            System.out.println(result);\n        } catch (IOException e) {\n            throw new RuntimeException();\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        } finally {\n            close(null, preparedStatement, null);\n        }\n\n        return connection;\n    }\n\n    private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n        if (resultSet != null) {\n            try {\n                resultSet.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (statement != null) {\n            try {\n                statement.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (null != connection) {\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n}\n
","site":{"data":{}},"excerpt":"","more":"
package com.jdbc;\n\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.sql.*;\nimport java.util.Properties;\n\n/**\n * Created by zj on 2017/2/10.\n */\npublic class ACID {\n    /*\n        JDBC实现数据库事务操作\n        1.原子性(事务不可分割)\n        2.一致性(一致性状态-->另一个一致性状态)\n        3.隔离性(类似于加锁, 某一刻一个数据只能被一个事务操作)\n        4.持久性(事务提交,数据库的改变就是永久的)\n\n        要实现 需要多个操作使用同一个连接(Connection)\n     */\n    @Test\n    public void test() {\n        Connection connection = null;\n        try {\n            connection = getConnection("update student set f_age = f_age + " + 2 + " where f_id = " + 1, null);\n            connection = getConnection("update student set f_age = f_age * " + 10 + " where f_id = " + 1, connection);\n            connection.commit();//如果前面的操作都成功,手动提交事务\n        } catch (SQLException e) {\n            if (connection != null) {\n                try {\n                    connection.rollback();//有异常就回滚事务\n                } catch (SQLException e1) {\n                    e1.printStackTrace();\n                }\n            }\n        } finally {\n            close(connection, null, null);//关闭\n        }\n    }\n\n    private Connection getConnection(String sql, Connection connection) throws SQLException {\n        int result;\n        PreparedStatement preparedStatement = null;\n        try {\n            Properties properties = new Properties();\n            properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n            String dirverClass = properties.getProperty("driver");\n            String url = properties.getProperty("jdbc_url");\n            String user = properties.getProperty("user");\n            String password = properties.getProperty("password");\n\n            Class.forName(dirverClass);\n            if (connection == null) {\n                connection = DriverManager.getConnection(url, user, password);\n                //取消自动提交(Connection的默认提交行为)\n                connection.setAutoCommit(false);\n            }\n            preparedStatement = connection.prepareStatement(sql);\n            result = preparedStatement.executeUpdate();\n            System.out.println(result);\n        } catch (IOException e) {\n            throw new RuntimeException();\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        } finally {\n            close(null, preparedStatement, null);\n        }\n\n        return connection;\n    }\n\n    private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n        if (resultSet != null) {\n            try {\n                resultSet.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (statement != null) {\n            try {\n                statement.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (null != connection) {\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n\n}\n
"},{"title":"JDBC工具类-利用java反射机制","comments":1,"description":null,"date":"2017-02-07T16:00:00.000Z","_content":"\n1. 利用java反射机制,动态进行数据库操作\n2. 可以对不同的表(对应相应的实体类)进行数据库操作而不需要修改操作代码\n3. 查询得到的数据通过反射机制已经动态赋值到实体对象中\n\n----\n\n\n![](http://www.zj2626.github.io/wp-content/uploads/2017/02/jdbc.png)\n\n\n\n---\n\n package com.jdbc;\n \n import org.junit.Test;\n \n import java.io.IOException;\n import java.lang.reflect.Field;\n import java.sql.*;\n import java.util.*;\n \n /**\n * Created by zj2626 on 17-1-19.\n */\n \n public class utilsTest {\n @Test\n public void test() {\"\"\n /**\n * 带占位符和别名的sql语句 查询\n * 别名是为了匹配数据库中字段名与实体类中属性的差异(如数据库表中列f_id对应类中id属性)\n */\n String sql = \"SELECT f_id id, f_name name, f_age age FROM student WHERE f_id = ?\";\n \n List list = query(Student.class, sql, 122);\n System.out.println(list != null ? \"查询到的数据有\" + list.size() + \"条\" : \"没查到!!!\");\n }\n \n // List中第一个T是泛型的声明,使T有意义,表示这是一个泛型方法\n // (即告诉人们T代表任意类型,每次只能表示一个类型)\n \n private List query(Class clazz, String sql, Object... args) {//使用可变参数表示查询条件\n List list = new ArrayList(); //用来存放查询到的结果\n \n Connection connection = null;\n PreparedStatement state = null;\n ResultSet resultSet = null;\n \n try {\n Properties properties = new Properties();\n //读取文件中的数据库配置信息赋值给各个变量\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n Class.forName(dirverClass);\n connection = DriverManager.getConnection(url, user, password);\n state = connection.prepareStatement(sql);\n for (int i = 0; i < args.length; i++) {\n state.setObject(i + 1, args[i]); //遍历设置占位符的值\n }\n \n /*\n 查询过程:\n 1.先利用sql语句进行查询\n 2.利用反射新建类的实体\n 3.获得sql语句中的别名,(tongg ResultSet的元数据对象--ReslutSetMetaData,其可以从结果集中获得所有信息,包括列名,别名等)\n 4.确定别名对应的属性并赋值给属性\n */\n resultSet = state.executeQuery();//查询并返回结果集\n \n ResultSetMetaData metaData = resultSet.getMetaData();//得到结果集的元数据对象\n while (resultSet.next()) {\n //利用反射新建类的实体\n T entity = null;\n \n int len = metaData.getColumnCount();\n for (int i = 0; i < len; i++) {\n String columnName = metaData.getColumnName(i + 1);//遍历查看列名(这里并没用到)\n String columnLabel = metaData.getColumnLabel(i + 1);//遍历获取列的别名\n System.out.println(columnName + \"--\" + columnLabel);\n Object columnValue = resultSet.getObject(columnLabel);//获取指定别名的列所对应的值\n \n entity = clazz.newInstance();//newInstance()调用newInstance()必须有无参构造方法\n \n //获取类的指定属性(一切皆对象,属性也是对象,都是Field类的实例)\n // Field field = clazz.getDeclaredField(columnLabel);//注:getField只能获得public字段\n // field.setAccessible(true);//设置为可访问(属性为private 不能直接赋值)\n // field.set(entity, columnValue);//为指定的属性赋值\n \n //赋值方法2: 以上三行的赋值功能可以用apache提供的一个工具类实现\n BeanUtils.setProperty(entity, columnLabel, columnValue);//该方法是通过实体中的属性的setter方法实现的\n }\n list.add(entity);\n }\n } catch (SQLException | IOException | ReflectiveOperationException e) {\n e.printStackTrace();\n } finally {\n close(connection, state, resultSet);\n }\n return list;\n }\n \n private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (statement != null) {\n try {\n statement.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n \n## 注:如果是插入一条或者多条数据可以用下面的过程获取到插入后自动生成的主键()\n\n\n\t\t //上面是获取connection创建PreparedStatement对象\n\t\t state.executeUpdate();\t\t\n //获取生成的新所有主键\n\t\t\t//返回的resultSet中只有一列--> 列名:GENERATED_KEY\n resultSet = state.getGeneratedKeys();\n while (resultSet.next()) {\n System.out.println(resultSet.getObject(1));\n }","source":"_posts/2017_jdbc_reflect.md","raw":"---\ntitle: JDBC工具类-利用java反射机制\n\ncomments: true \n\ntags: \n - CRUD\n - 工具类\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-08\n \n---\n\n1. 利用java反射机制,动态进行数据库操作\n2. 可以对不同的表(对应相应的实体类)进行数据库操作而不需要修改操作代码\n3. 查询得到的数据通过反射机制已经动态赋值到实体对象中\n\n----\n\n\n![](http://www.zj2626.github.io/wp-content/uploads/2017/02/jdbc.png)\n\n\n\n---\n\n package com.jdbc;\n \n import org.junit.Test;\n \n import java.io.IOException;\n import java.lang.reflect.Field;\n import java.sql.*;\n import java.util.*;\n \n /**\n * Created by zj2626 on 17-1-19.\n */\n \n public class utilsTest {\n @Test\n public void test() {\"\"\n /**\n * 带占位符和别名的sql语句 查询\n * 别名是为了匹配数据库中字段名与实体类中属性的差异(如数据库表中列f_id对应类中id属性)\n */\n String sql = \"SELECT f_id id, f_name name, f_age age FROM student WHERE f_id = ?\";\n \n List list = query(Student.class, sql, 122);\n System.out.println(list != null ? \"查询到的数据有\" + list.size() + \"条\" : \"没查到!!!\");\n }\n \n // List中第一个T是泛型的声明,使T有意义,表示这是一个泛型方法\n // (即告诉人们T代表任意类型,每次只能表示一个类型)\n \n private List query(Class clazz, String sql, Object... args) {//使用可变参数表示查询条件\n List list = new ArrayList(); //用来存放查询到的结果\n \n Connection connection = null;\n PreparedStatement state = null;\n ResultSet resultSet = null;\n \n try {\n Properties properties = new Properties();\n //读取文件中的数据库配置信息赋值给各个变量\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n \n Class.forName(dirverClass);\n connection = DriverManager.getConnection(url, user, password);\n state = connection.prepareStatement(sql);\n for (int i = 0; i < args.length; i++) {\n state.setObject(i + 1, args[i]); //遍历设置占位符的值\n }\n \n /*\n 查询过程:\n 1.先利用sql语句进行查询\n 2.利用反射新建类的实体\n 3.获得sql语句中的别名,(tongg ResultSet的元数据对象--ReslutSetMetaData,其可以从结果集中获得所有信息,包括列名,别名等)\n 4.确定别名对应的属性并赋值给属性\n */\n resultSet = state.executeQuery();//查询并返回结果集\n \n ResultSetMetaData metaData = resultSet.getMetaData();//得到结果集的元数据对象\n while (resultSet.next()) {\n //利用反射新建类的实体\n T entity = null;\n \n int len = metaData.getColumnCount();\n for (int i = 0; i < len; i++) {\n String columnName = metaData.getColumnName(i + 1);//遍历查看列名(这里并没用到)\n String columnLabel = metaData.getColumnLabel(i + 1);//遍历获取列的别名\n System.out.println(columnName + \"--\" + columnLabel);\n Object columnValue = resultSet.getObject(columnLabel);//获取指定别名的列所对应的值\n \n entity = clazz.newInstance();//newInstance()调用newInstance()必须有无参构造方法\n \n //获取类的指定属性(一切皆对象,属性也是对象,都是Field类的实例)\n // Field field = clazz.getDeclaredField(columnLabel);//注:getField只能获得public字段\n // field.setAccessible(true);//设置为可访问(属性为private 不能直接赋值)\n // field.set(entity, columnValue);//为指定的属性赋值\n \n //赋值方法2: 以上三行的赋值功能可以用apache提供的一个工具类实现\n BeanUtils.setProperty(entity, columnLabel, columnValue);//该方法是通过实体中的属性的setter方法实现的\n }\n list.add(entity);\n }\n } catch (SQLException | IOException | ReflectiveOperationException e) {\n e.printStackTrace();\n } finally {\n close(connection, state, resultSet);\n }\n return list;\n }\n \n private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n if (resultSet != null) {\n try {\n resultSet.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (statement != null) {\n try {\n statement.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n if (null != connection) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n \n## 注:如果是插入一条或者多条数据可以用下面的过程获取到插入后自动生成的主键()\n\n\n\t\t //上面是获取connection创建PreparedStatement对象\n\t\t state.executeUpdate();\t\t\n //获取生成的新所有主键\n\t\t\t//返回的resultSet中只有一列--> 列名:GENERATED_KEY\n resultSet = state.getGeneratedKeys();\n while (resultSet.next()) {\n System.out.println(resultSet.getObject(1));\n }","slug":"2017_jdbc_reflect","published":1,"updated":"2021-03-10T13:50:15.249Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwo003o24ujgv3wwme2","content":"
    \n
  1. 利用java反射机制,动态进行数据库操作
  2. \n
  3. 可以对不同的表(对应相应的实体类)进行数据库操作而不需要修改操作代码
  4. \n
  5. 查询得到的数据通过反射机制已经动态赋值到实体对象中
  6. \n
\n
\n

\n\n
\n
package com.jdbc;\n\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.sql.*;\nimport java.util.*;\n\n/**\n * Created by zj2626 on 17-1-19.\n */\n\npublic class utilsTest {\n    @Test\n    public void test() {<img src="http://www.zj2626.github.io/wp-content/uploads/2017/02/jdbc-300x115.png" alt="" width="300" height="115" class="alignnone size-medium wp-image-233" />\n        /**\n         * 带占位符和别名的sql语句 查询\n         * 别名是为了匹配数据库中字段名与实体类中属性的差异(如数据库表中列f_id对应类中id属性)\n         */\n        String sql = "SELECT f_id id, f_name name, f_age age FROM student WHERE f_id = ?";\n\n        List<Student> list = query(Student.class, sql, 122);\n        System.out.println(list != null ? "查询到的数据有" + list.size() + "条" : "没查到!!!");\n    }\n\n    //<T> List<T>中第一个T是泛型的声明,使T有意义,表示这是一个泛型方法\n    // (即告诉人们T代表任意类型,每次只能表示一个类型)\n\n    private <T> List<T> query(Class<T> clazz, String sql, Object... args) {//使用可变参数表示查询条件\n        List<T> list = new ArrayList<T>(); //用来存放查询到的结果\n\n        Connection connection = null;\n        PreparedStatement state = null;\n        ResultSet resultSet = null;\n\n        try {\n            Properties properties = new Properties();\n            //读取文件中的数据库配置信息赋值给各个变量\n            properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n            String dirverClass = properties.getProperty("driver");\n            String url = properties.getProperty("jdbc_url");\n            String user = properties.getProperty("user");\n            String password = properties.getProperty("password");\n\n            Class.forName(dirverClass);\n            connection = DriverManager.getConnection(url, user, password);\n            state = connection.prepareStatement(sql);\n            for (int i = 0; i < args.length; i++) {\n                state.setObject(i + 1, args[i]); //遍历设置占位符的值\n            }\n\n            /*\n              查询过程:\n              1.先利用sql语句进行查询\n              2.利用反射新建类的实体\n              3.获得sql语句中的别名,(tongg ResultSet的元数据对象--ReslutSetMetaData,其可以从结果集中获得所有信息,包括列名,别名等)\n              4.确定别名对应的属性并赋值给属性\n             */\n            resultSet = state.executeQuery();//查询并返回结果集\n\n            ResultSetMetaData metaData = resultSet.getMetaData();//得到结果集的元数据对象\n            while (resultSet.next()) {\n                //利用反射新建类的实体\n                T entity = null;\n\n                int len = metaData.getColumnCount();\n                for (int i = 0; i < len; i++) {\n                    String columnName = metaData.getColumnName(i + 1);//遍历查看列名(这里并没用到)\n                    String columnLabel = metaData.getColumnLabel(i + 1);//遍历获取列的别名\n                    System.out.println(columnName + "--" + columnLabel);\n                    Object columnValue = resultSet.getObject(columnLabel);//获取指定别名的列所对应的值\n\n                    entity = clazz.newInstance();//newInstance()调用newInstance()必须有无参构造方法\n\n                    //获取类的指定属性(一切皆对象,属性也是对象,都是Field类的实例)\n//                    Field field = clazz.getDeclaredField(columnLabel);//注:getField只能获得public字段\n//                    field.setAccessible(true);//设置为可访问(属性为private 不能直接赋值)\n//                    field.set(entity, columnValue);//为指定的属性赋值\n\n                    //赋值方法2: 以上三行的赋值功能可以用apache提供的一个工具类实现\n                    BeanUtils.setProperty(entity, columnLabel, columnValue);//该方法是通过实体中的属性的setter方法实现的\n                }\n                list.add(entity);\n            }\n        } catch (SQLException | IOException | ReflectiveOperationException e) {\n            e.printStackTrace();\n        } finally {\n            close(connection, state, resultSet);\n        }\n        return list;\n    }\n\n    private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n        if (resultSet != null) {\n            try {\n                resultSet.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (statement != null) {\n            try {\n                statement.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (null != connection) {\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n

注:如果是插入一条或者多条数据可以用下面的过程获取到插入后自动生成的主键()

//上面是获取connection创建PreparedStatement对象\n state.executeUpdate();        \n //获取生成的新所有主键\n //返回的resultSet中只有一列--> 列名:GENERATED_KEY\n resultSet = state.getGeneratedKeys();\n while (resultSet.next()) {\n     System.out.println(resultSet.getObject(1));\n }\n
","site":{"data":{}},"excerpt":"
    \n
  1. 利用java反射机制,动态进行数据库操作
  2. \n
  3. 可以对不同的表(对应相应的实体类)进行数据库操作而不需要修改操作代码
  4. \n
  5. 查询得到的数据通过反射机制已经动态赋值到实体对象中
  6. \n
\n
\n

","more":"
\n
package com.jdbc;\n\nimport org.junit.Test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.sql.*;\nimport java.util.*;\n\n/**\n * Created by zj2626 on 17-1-19.\n */\n\npublic class utilsTest {\n    @Test\n    public void test() {<img src="http://www.zj2626.github.io/wp-content/uploads/2017/02/jdbc-300x115.png" alt="" width="300" height="115" class="alignnone size-medium wp-image-233" />\n        /**\n         * 带占位符和别名的sql语句 查询\n         * 别名是为了匹配数据库中字段名与实体类中属性的差异(如数据库表中列f_id对应类中id属性)\n         */\n        String sql = "SELECT f_id id, f_name name, f_age age FROM student WHERE f_id = ?";\n\n        List<Student> list = query(Student.class, sql, 122);\n        System.out.println(list != null ? "查询到的数据有" + list.size() + "条" : "没查到!!!");\n    }\n\n    //<T> List<T>中第一个T是泛型的声明,使T有意义,表示这是一个泛型方法\n    // (即告诉人们T代表任意类型,每次只能表示一个类型)\n\n    private <T> List<T> query(Class<T> clazz, String sql, Object... args) {//使用可变参数表示查询条件\n        List<T> list = new ArrayList<T>(); //用来存放查询到的结果\n\n        Connection connection = null;\n        PreparedStatement state = null;\n        ResultSet resultSet = null;\n\n        try {\n            Properties properties = new Properties();\n            //读取文件中的数据库配置信息赋值给各个变量\n            properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n            String dirverClass = properties.getProperty("driver");\n            String url = properties.getProperty("jdbc_url");\n            String user = properties.getProperty("user");\n            String password = properties.getProperty("password");\n\n            Class.forName(dirverClass);\n            connection = DriverManager.getConnection(url, user, password);\n            state = connection.prepareStatement(sql);\n            for (int i = 0; i < args.length; i++) {\n                state.setObject(i + 1, args[i]); //遍历设置占位符的值\n            }\n\n            /*\n              查询过程:\n              1.先利用sql语句进行查询\n              2.利用反射新建类的实体\n              3.获得sql语句中的别名,(tongg ResultSet的元数据对象--ReslutSetMetaData,其可以从结果集中获得所有信息,包括列名,别名等)\n              4.确定别名对应的属性并赋值给属性\n             */\n            resultSet = state.executeQuery();//查询并返回结果集\n\n            ResultSetMetaData metaData = resultSet.getMetaData();//得到结果集的元数据对象\n            while (resultSet.next()) {\n                //利用反射新建类的实体\n                T entity = null;\n\n                int len = metaData.getColumnCount();\n                for (int i = 0; i < len; i++) {\n                    String columnName = metaData.getColumnName(i + 1);//遍历查看列名(这里并没用到)\n                    String columnLabel = metaData.getColumnLabel(i + 1);//遍历获取列的别名\n                    System.out.println(columnName + "--" + columnLabel);\n                    Object columnValue = resultSet.getObject(columnLabel);//获取指定别名的列所对应的值\n\n                    entity = clazz.newInstance();//newInstance()调用newInstance()必须有无参构造方法\n\n                    //获取类的指定属性(一切皆对象,属性也是对象,都是Field类的实例)\n//                    Field field = clazz.getDeclaredField(columnLabel);//注:getField只能获得public字段\n//                    field.setAccessible(true);//设置为可访问(属性为private 不能直接赋值)\n//                    field.set(entity, columnValue);//为指定的属性赋值\n\n                    //赋值方法2: 以上三行的赋值功能可以用apache提供的一个工具类实现\n                    BeanUtils.setProperty(entity, columnLabel, columnValue);//该方法是通过实体中的属性的setter方法实现的\n                }\n                list.add(entity);\n            }\n        } catch (SQLException | IOException | ReflectiveOperationException e) {\n            e.printStackTrace();\n        } finally {\n            close(connection, state, resultSet);\n        }\n        return list;\n    }\n\n    private void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {\n        if (resultSet != null) {\n            try {\n                resultSet.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (statement != null) {\n            try {\n                statement.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n        if (null != connection) {\n            try {\n                connection.close();\n            } catch (SQLException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n

注:如果是插入一条或者多条数据可以用下面的过程获取到插入后自动生成的主键()

//上面是获取connection创建PreparedStatement对象\n state.executeUpdate();        \n //获取生成的新所有主键\n //返回的resultSet中只有一列--> 列名:GENERATED_KEY\n resultSet = state.getGeneratedKeys();\n while (resultSet.next()) {\n     System.out.println(resultSet.getObject(1));\n }\n
"},{"title":"数据库连接池作用(转载)","comments":1,"description":null,"date":"2017-02-11T16:00:00.000Z","_content":"\n连接池的作用就是为了提高性能。\n\n 连接池的作用:连接池是将已经创建好的连接保存在池中,\n\t\t\t 当有请求来时,直接使用已经创建好的连接对数据库进行访问。\n\t\t\t 这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。\n\t\t\t \n\t\t\t \n\n\n基本原理是这样的:\n\n(1)建立数据库连接池对象(服务器启动)。\n\n(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。\n\n(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。\n\n(4)存取数据库。\n\n(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。\n\n(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。\n\n---\n\n1 .连接池的概念和为什么要使用连接池?\n\n 连接池放了N个Connection对象,本质上放在内存当中,在内存中划出一块缓存对象,\n\t\t应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。\n\t\t\n2 .如果不使用连接池会出现的情况:\n\n\ta.占用服务器的内存资源\n\t b.导致服务器的速度非常慢\n\n3 .应用连接池的三种方式:\n\n\ta.自定义连接池\n\tb.使用第三方连接池\n\tc.使用服务器自带的连接池\n\t\n\t\n\t\n---\n连接池一般比直接连接更有优越性,因为它提高了性能的同时还保存了宝贵的资源。在整个应用程序的使用过程,当中重复的打开直接连接将导致性能的下降。而池连接只在服务器启动时打开一次,从而消除了这种性能问题。\n\n连接池主要考虑的是性能,每次获取连接和释放连接都有很大的工作量,会对性能有很大影响;而对资源来说起的是反作用,因为保存一定数量的连接是要消耗内存的。应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。所以一般要建立连接池,而连接的数量要适当,不能太大,太大会过多消耗资源。(所以,考虑2个方面,一个是内存,另一个是资源)。\n\n连接池就是为了避免重复多次的打开数据库连接而造成的性能的下降和系统资源的浪费。","source":"_posts/2017_ljc.md","raw":"---\ntitle: 数据库连接池作用(转载)\n\ncomments: true \n\ntags: \n - \n\ncategories: \n - 数据库连接池\n\ndescription: \n\ndate: 2017-02-12\n \n---\n\n连接池的作用就是为了提高性能。\n\n 连接池的作用:连接池是将已经创建好的连接保存在池中,\n\t\t\t 当有请求来时,直接使用已经创建好的连接对数据库进行访问。\n\t\t\t 这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。\n\t\t\t \n\t\t\t \n\n\n基本原理是这样的:\n\n(1)建立数据库连接池对象(服务器启动)。\n\n(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。\n\n(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。\n\n(4)存取数据库。\n\n(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。\n\n(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。\n\n---\n\n1 .连接池的概念和为什么要使用连接池?\n\n 连接池放了N个Connection对象,本质上放在内存当中,在内存中划出一块缓存对象,\n\t\t应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。\n\t\t\n2 .如果不使用连接池会出现的情况:\n\n\ta.占用服务器的内存资源\n\t b.导致服务器的速度非常慢\n\n3 .应用连接池的三种方式:\n\n\ta.自定义连接池\n\tb.使用第三方连接池\n\tc.使用服务器自带的连接池\n\t\n\t\n\t\n---\n连接池一般比直接连接更有优越性,因为它提高了性能的同时还保存了宝贵的资源。在整个应用程序的使用过程,当中重复的打开直接连接将导致性能的下降。而池连接只在服务器启动时打开一次,从而消除了这种性能问题。\n\n连接池主要考虑的是性能,每次获取连接和释放连接都有很大的工作量,会对性能有很大影响;而对资源来说起的是反作用,因为保存一定数量的连接是要消耗内存的。应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。所以一般要建立连接池,而连接的数量要适当,不能太大,太大会过多消耗资源。(所以,考虑2个方面,一个是内存,另一个是资源)。\n\n连接池就是为了避免重复多次的打开数据库连接而造成的性能的下降和系统资源的浪费。","slug":"2017_ljc","published":1,"updated":"2018-01-13T02:29:22.257Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwo003p24uj5oumkust","content":"

连接池的作用就是为了提高性能。

\n
连接池的作用:连接池是将已经创建好的连接保存在池中,\n      当有请求来时,直接使用已经创建好的连接对数据库进行访问。\n      这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。\n
\n

基本原理是这样的:

\n

(1)建立数据库连接池对象(服务器启动)。

\n

(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。

\n

(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。

\n

(4)存取数据库。

\n

(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。

\n

(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。

\n
\n

1 .连接池的概念和为什么要使用连接池?

\n
连接池放了N个Connection对象,本质上放在内存当中,在内存中划出一块缓存对象,\n    应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。\n

2 .如果不使用连接池会出现的情况:

\n
a.占用服务器的内存资源\n b.导致服务器的速度非常慢\n

3 .应用连接池的三种方式:

\n
a.自定义连接池\nb.使用第三方连接池\nc.使用服务器自带的连接池\n

\n

连接池一般比直接连接更有优越性,因为它提高了性能的同时还保存了宝贵的资源。在整个应用程序的使用过程,当中重复的打开直接连接将导致性能的下降。而池连接只在服务器启动时打开一次,从而消除了这种性能问题。

\n

连接池主要考虑的是性能,每次获取连接和释放连接都有很大的工作量,会对性能有很大影响;而对资源来说起的是反作用,因为保存一定数量的连接是要消耗内存的。应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。所以一般要建立连接池,而连接的数量要适当,不能太大,太大会过多消耗资源。(所以,考虑2个方面,一个是内存,另一个是资源)。

\n

连接池就是为了避免重复多次的打开数据库连接而造成的性能的下降和系统资源的浪费。

\n","site":{"data":{}},"excerpt":"

连接池的作用就是为了提高性能。

\n
连接池的作用:连接池是将已经创建好的连接保存在池中,\n      当有请求来时,直接使用已经创建好的连接对数据库进行访问。\n      这样省略了创建连接和销毁连接的过程。这样性能上得到了提高。\n
","more":"

基本原理是这样的:

\n

(1)建立数据库连接池对象(服务器启动)。

\n

(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。

\n

(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。

\n

(4)存取数据库。

\n

(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。

\n

(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。

\n
\n

1 .连接池的概念和为什么要使用连接池?

\n
连接池放了N个Connection对象,本质上放在内存当中,在内存中划出一块缓存对象,\n    应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。\n

2 .如果不使用连接池会出现的情况:

\n
a.占用服务器的内存资源\n b.导致服务器的速度非常慢\n

3 .应用连接池的三种方式:

\n
a.自定义连接池\nb.使用第三方连接池\nc.使用服务器自带的连接池\n

\n

连接池一般比直接连接更有优越性,因为它提高了性能的同时还保存了宝贵的资源。在整个应用程序的使用过程,当中重复的打开直接连接将导致性能的下降。而池连接只在服务器启动时打开一次,从而消除了这种性能问题。

\n

连接池主要考虑的是性能,每次获取连接和释放连接都有很大的工作量,会对性能有很大影响;而对资源来说起的是反作用,因为保存一定数量的连接是要消耗内存的。应用程序每次从池里获得Connection对象,而不是直接从数据里获得,这样不占用服务器的内存资源。所以一般要建立连接池,而连接的数量要适当,不能太大,太大会过多消耗资源。(所以,考虑2个方面,一个是内存,另一个是资源)。

\n

连接池就是为了避免重复多次的打开数据库连接而造成的性能的下降和系统资源的浪费。

"},{"title":"sax解析xml文档(用javabean封装xml文档)","comments":1,"description":"sax解析xml文档","date":"2017-02-04T16:00:00.000Z","_content":"\n**被解析的xml示例**\n\n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n \n\n\n\n\n**封装用的javabean**\n```java\npackage cn.sax;\n//这是封装用的类\npublic class BookObject {\n private String name;\n private String author;\n private String price;\n\n public String getName() {\n return name;\n }\n public void setName(String name) {\n this.name = name;\n }\n public String getAuthor() {\n return author;\n }\n public void setAuthor(String author) {\n this.author = author;\n }\n public String getPrice() {\n return price;\n }\n public void setPrice(String price) {\n this.price = price;\n }\n}\n**解析**\npackage cn.sax;\n\nimport java.awt.print.Book;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\n\nimport org.junit.Test;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\nimport org.xml.sax.helpers.DefaultHandler;\n\npublic class sax解析xml_javabean封装xml {\n\n @Test\n public void main() throws ParserConfigurationException, SAXException, IOException {\n //1.创建解析工厂 抽象工厂\n SAXParserFactory factory = SAXParserFactory.newInstance();\n //2.得到解析器\n SAXParser parser = factory.newSAXParser();\n //3.得到读取器\n XMLReader reader = parser.getXMLReader();\n //4.设置内容处理器 读取使xml内容放在list中的book对象中\n BeanListHandler hand = new BeanListHandler();\n reader.setContentHandler(hand);\n //5.读取文档内容 解析xml文档\n reader.parse(\"src/book.xml\");\n List list = hand.getBooks();\n System.out.println(list);\n }\n}\n\n//把xml文档中的每一本书封装到每个book对象中 并把多个book对象放在List集合中返回\nclass BeanListHandler extends DefaultHandler{\n private List list = new ArrayList();\n private String currentTag;//解析到的当前的标签名称(所有)\n private BookObject book;//book对象封装得到的book标签\n\n @Override\n public void startElement(String uri, String localName, String qName,\n Attributes attributes) throws SAXException {//开始标签\n currentTag = qName;\n if(\"书\".equals(currentTag)){\n //如果是书 就要用一个book对象来封装这个书\n book = new BookObject();\n }\n }\n\n @Override\n public void characters(char[] ch, int start, int length)\n throws SAXException { //内容\n if(\"书名\".equals(currentTag)){//如果标签是 书名 则创造字符串对象放标签的内容\n String name = new String(ch, start, length);\n book.setName(name);\n }\n if(\"作者\".equals(currentTag)){\n String author = new String(ch, start, length);\n book.setAuthor(author);\n }\n if(\"售价\".equals(currentTag)){\n String price = new String(ch, start, length);\n book.setPrice(price);\n }\n }\n\n @Override\n public void endElement(String uri, String localName, String qName)\n throws SAXException { //结束标签\n currentTag = null;//currentTag不能不清空 以为解析xml文档会读取到空格和换行 干扰 空指针异常\n if (\"书\".equals(qName)) {\n list.add(book);//把book对象加入到list中\n book = null;//book对象置空\n }\n }\n\n //由于list对象是私有的 所以需要这个 \n public List getBooks() {\n return list;\n }\n}\n```","source":"_posts/2017_sax2.md","raw":"---\ntitle: sax解析xml文档(用javabean封装xml文档)\n\ncomments: true \n\ntags: sax\n\ncategories: \n - DOM操作 \n - XML\n\ndescription: sax解析xml文档\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n**被解析的xml示例**\n\n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n \n\n\n\n\n**封装用的javabean**\n```java\npackage cn.sax;\n//这是封装用的类\npublic class BookObject {\n private String name;\n private String author;\n private String price;\n\n public String getName() {\n return name;\n }\n public void setName(String name) {\n this.name = name;\n }\n public String getAuthor() {\n return author;\n }\n public void setAuthor(String author) {\n this.author = author;\n }\n public String getPrice() {\n return price;\n }\n public void setPrice(String price) {\n this.price = price;\n }\n}\n**解析**\npackage cn.sax;\n\nimport java.awt.print.Book;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\n\nimport org.junit.Test;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\nimport org.xml.sax.helpers.DefaultHandler;\n\npublic class sax解析xml_javabean封装xml {\n\n @Test\n public void main() throws ParserConfigurationException, SAXException, IOException {\n //1.创建解析工厂 抽象工厂\n SAXParserFactory factory = SAXParserFactory.newInstance();\n //2.得到解析器\n SAXParser parser = factory.newSAXParser();\n //3.得到读取器\n XMLReader reader = parser.getXMLReader();\n //4.设置内容处理器 读取使xml内容放在list中的book对象中\n BeanListHandler hand = new BeanListHandler();\n reader.setContentHandler(hand);\n //5.读取文档内容 解析xml文档\n reader.parse(\"src/book.xml\");\n List list = hand.getBooks();\n System.out.println(list);\n }\n}\n\n//把xml文档中的每一本书封装到每个book对象中 并把多个book对象放在List集合中返回\nclass BeanListHandler extends DefaultHandler{\n private List list = new ArrayList();\n private String currentTag;//解析到的当前的标签名称(所有)\n private BookObject book;//book对象封装得到的book标签\n\n @Override\n public void startElement(String uri, String localName, String qName,\n Attributes attributes) throws SAXException {//开始标签\n currentTag = qName;\n if(\"书\".equals(currentTag)){\n //如果是书 就要用一个book对象来封装这个书\n book = new BookObject();\n }\n }\n\n @Override\n public void characters(char[] ch, int start, int length)\n throws SAXException { //内容\n if(\"书名\".equals(currentTag)){//如果标签是 书名 则创造字符串对象放标签的内容\n String name = new String(ch, start, length);\n book.setName(name);\n }\n if(\"作者\".equals(currentTag)){\n String author = new String(ch, start, length);\n book.setAuthor(author);\n }\n if(\"售价\".equals(currentTag)){\n String price = new String(ch, start, length);\n book.setPrice(price);\n }\n }\n\n @Override\n public void endElement(String uri, String localName, String qName)\n throws SAXException { //结束标签\n currentTag = null;//currentTag不能不清空 以为解析xml文档会读取到空格和换行 干扰 空指针异常\n if (\"书\".equals(qName)) {\n list.add(book);//把book对象加入到list中\n book = null;//book对象置空\n }\n }\n\n //由于list对象是私有的 所以需要这个 \n public List getBooks() {\n return list;\n }\n}\n```","slug":"2017_sax2","published":1,"updated":"2018-01-13T02:29:22.260Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwr003u24uj81leu6m5","content":"

被解析的xml示例

\n
<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<书架>\n    <书>\n        <书名>java实战</书名>\n        <作者>张三</作者>\n        <售价>121元</售价>\n        <售价>12元</售价>\n    </书>\n    <书>\n        <书名 color="yellow" name="XXX">c测试</书名>\n        <作者>李四</作者>\n        <售价 color="rrr">54元</售价>\n        <售价>12元</售价></书>\n</书架>\n
\n

封装用的javabean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package cn.sax;
//这是封装用的类
public class BookObject {
private String name;
private String author;
private String price;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
**解析**
package cn.sax;

import java.awt.print.Book;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

public class sax解析xml_javabean封装xml {

@Test
public void main() throws ParserConfigurationException, SAXException, IOException {
//1.创建解析工厂 抽象工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser parser = factory.newSAXParser();
//3.得到读取器
XMLReader reader = parser.getXMLReader();
//4.设置内容处理器 读取使xml内容放在list中的book对象中
BeanListHandler hand = new BeanListHandler();
reader.setContentHandler(hand);
//5.读取文档内容 解析xml文档
reader.parse(\"src/book.xml\");
List list = hand.getBooks();
System.out.println(list);
}
}

//把xml文档中的每一本书封装到每个book对象中 并把多个book对象放在List集合中返回
class BeanListHandler extends DefaultHandler{
private List list = new ArrayList();
private String currentTag;//解析到的当前的标签名称(所有)
private BookObject book;//book对象封装得到的book标签

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {//开始标签
currentTag = qName;
if(\"书\".equals(currentTag)){
//如果是书 就要用一个book对象来封装这个书
book = new BookObject();
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException { //内容
if(\"书名\".equals(currentTag)){//如果标签是 书名 则创造字符串对象放标签的内容
String name = new String(ch, start, length);
book.setName(name);
}
if(\"作者\".equals(currentTag)){
String author = new String(ch, start, length);
book.setAuthor(author);
}
if(\"售价\".equals(currentTag)){
String price = new String(ch, start, length);
book.setPrice(price);
}
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException { //结束标签
currentTag = null;//currentTag不能不清空 以为解析xml文档会读取到空格和换行 干扰 空指针异常
if (\"书\".equals(qName)) {
list.add(book);//把book对象加入到list中
book = null;//book对象置空
}
}

//由于list对象是私有的 所以需要这个
public List getBooks() {
return list;
}
}

\n","site":{"data":{}},"excerpt":"

被解析的xml示例

\n
<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<书架>\n    <书>\n        <书名>java实战</书名>\n        <作者>张三</作者>\n        <售价>121元</售价>\n        <售价>12元</售价>\n    </书>\n    <书>\n        <书名 color="yellow" name="XXX">c测试</书名>\n        <作者>李四</作者>\n        <售价 color="rrr">54元</售价>\n        <售价>12元</售价></书>\n</书架>\n
","more":"

封装用的javabean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package cn.sax;
//这是封装用的类
public class BookObject {
private String name;
private String author;
private String price;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
**解析**
package cn.sax;

import java.awt.print.Book;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

public class sax解析xml_javabean封装xml {

@Test
public void main() throws ParserConfigurationException, SAXException, IOException {
//1.创建解析工厂 抽象工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser parser = factory.newSAXParser();
//3.得到读取器
XMLReader reader = parser.getXMLReader();
//4.设置内容处理器 读取使xml内容放在list中的book对象中
BeanListHandler hand = new BeanListHandler();
reader.setContentHandler(hand);
//5.读取文档内容 解析xml文档
reader.parse(\"src/book.xml\");
List list = hand.getBooks();
System.out.println(list);
}
}

//把xml文档中的每一本书封装到每个book对象中 并把多个book对象放在List集合中返回
class BeanListHandler extends DefaultHandler{
private List list = new ArrayList();
private String currentTag;//解析到的当前的标签名称(所有)
private BookObject book;//book对象封装得到的book标签

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {//开始标签
currentTag = qName;
if(\"书\".equals(currentTag)){
//如果是书 就要用一个book对象来封装这个书
book = new BookObject();
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException { //内容
if(\"书名\".equals(currentTag)){//如果标签是 书名 则创造字符串对象放标签的内容
String name = new String(ch, start, length);
book.setName(name);
}
if(\"作者\".equals(currentTag)){
String author = new String(ch, start, length);
book.setAuthor(author);
}
if(\"售价\".equals(currentTag)){
String price = new String(ch, start, length);
book.setPrice(price);
}
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException { //结束标签
currentTag = null;//currentTag不能不清空 以为解析xml文档会读取到空格和换行 干扰 空指针异常
if (\"书\".equals(qName)) {
list.add(book);//把book对象加入到list中
book = null;//book对象置空
}
}

//由于list对象是私有的 所以需要这个
public List getBooks() {
return list;
}
}

"},{"title":"sax解析xml文档实现打印xml(遍历全部和指定位置)","comments":1,"description":"sax解析xml文档","date":"2017-02-04T16:00:00.000Z","_content":"\n**这是示例被解析xml**\n\n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n\n\n\n\n\n**遍历全部**\n```java\n\npackage cn.sax;\n\nimport java.io.IOException;\n\nimport javax.sql.rowset.spi.XmlReader;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\n\nimport org.junit.Test;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.ContentHandler;\nimport org.xml.sax.Locator;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\n\npublic class sax解析xml {\n @Test\n public void main() throws ParserConfigurationException, SAXException, IOException {\n //1.创建解析工厂 抽象工厂\n SAXParserFactory factory = SAXParserFactory.newInstance();\n //2.得到解析器\n SAXParser parser = factory.newSAXParser();\n //3.得到读取器\n XMLReader reader = parser.getXMLReader();\n //4.设置内容处理器\n reader.setContentHandler(new ListHandler());\n //5.读取文档内容 解析xml文档 解析一点就调用处理器处理 所以要先设置内容处理器\n reader.parse(\"src/book.xml\");\n }\n\n}\n\n//内容处理器 得到xml文档所有内容\nclass ListHandler implements ContentHandler{\n //实现接口中的方法\n @Override\n public void startElement(String uri, String localName, String qName,\n Attributes atts) throws SAXException {//当得到开始标签时 调用这个方法\n // TODO Auto-generated method stub\n System.out.print(\"<\" + qName );//打印\n\n //获取属性 还要判断属性有没有(是否为null)\n for (int i = 0; atts != null && i < atts.getLength(); i++) {\n String att_Name = atts.getQName(i);\n String att_Vlaue = atts.getValue(i);\n System.out.print(att_Name + \"=\" + att_Vlaue);\n }\n\n System.out.println( \">\");\n }\n\n @Override\n public void characters(char[] ch, int start, int length)\n throws SAXException { //当解析到标签中内容时就调用这个方法\n // TODO Auto-generated method stub\n System.out.println(new String(ch, start, length));//String解码(要解码的字符集,要解码的第一个byte位置,解码的长度)\n }\n\n @Override\n public void endElement(String uri, String localName, String qName)\n throws SAXException { //当解析到结束标签时 调用这个方法\n // TODO Auto-generated method stub\n System.out.println(\"\");\n }\n\n\n @Override\n public void setDocumentLocator(Locator locator) {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void startDocument() throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void endDocument() throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void startPrefixMapping(String prefix, String uri)\n throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void endPrefixMapping(String prefix) throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void ignorableWhitespace(char[] ch, int start, int length)\n throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void processingInstruction(String target, String data)\n throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void skippedEntity(String name) throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n}\n```\n\n**遍历指定标签**\n\n```java\n //打印指定标签的值(这里指定第二个作者的值)\npackage cn.sax;\n\nimport java.io.IOException;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\n\nimport org.junit.Test;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\nimport org.xml.sax.helpers.DefaultHandler;\n\n\npublic class sax解析获得指定标签的值{\n @Test\n public void main() throws ParserConfigurationException, SAXException, IOException {\n //1.创建解析工厂 抽象工厂\n SAXParserFactory factory = SAXParserFactory.newInstance();\n //2.得到解析器\n SAXParser parser = factory.newSAXParser();\n //3.得到读取器\n XMLReader reader = parser.getXMLReader();\n //4.设置内容处理器\n reader.setContentHandler(new TagValueHandler());\n //5.读取文档内容 解析xml文档\n reader.parse(\"src/book.xml\");\n }\n}\n\n//获取指定标签的值\nclass TagValueHandler extends DefaultHandler{\n private String currentTag;//用来记住当前解析到的是什么标签\n private int needNumber = 2; //需要的标签是第几个标签\n private int currentNumber; //当前解析到的是第几个标签\n\n @Override\n public void startElement(String uri, String localName, String qName,\n Attributes attributes) throws SAXException {\n // TODO Auto-generated method stub\n super.startElement(uri, localName, qName, attributes);//可以不写\n\n currentTag = qName;\n if(currentTag.equals(\"作者\")){\n currentNumber++;\n }\n }\n\n @Override\n public void endElement(String uri, String localName, String qName)\n throws SAXException {\n // TODO Auto-generated method stub\n super.endElement(uri, localName, qName);\n\n currentTag = null;//置空\n }\n\n @Override\n public void characters(char[] ch, int start, int length)\n throws SAXException {\n // TODO Auto-generated method stub\n super.characters(ch, start, length);\n\n if(\"作者\".equals(currentTag) && currentNumber==needNumber){ \n System.out.println(new String(ch, start, length));\n }\n }\n\n}\n```\n","source":"_posts/2017_sax.md","raw":"---\ntitle: sax解析xml文档实现打印xml(遍历全部和指定位置)\n\ncomments: true \n\ntags: sax\n\ncategories: \n - DOM操作 \n - XML\n\ndescription: sax解析xml文档\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n**这是示例被解析xml**\n\n \n <书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n \n\n\n\n\n\n**遍历全部**\n```java\n\npackage cn.sax;\n\nimport java.io.IOException;\n\nimport javax.sql.rowset.spi.XmlReader;\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\n\nimport org.junit.Test;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.ContentHandler;\nimport org.xml.sax.Locator;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\n\npublic class sax解析xml {\n @Test\n public void main() throws ParserConfigurationException, SAXException, IOException {\n //1.创建解析工厂 抽象工厂\n SAXParserFactory factory = SAXParserFactory.newInstance();\n //2.得到解析器\n SAXParser parser = factory.newSAXParser();\n //3.得到读取器\n XMLReader reader = parser.getXMLReader();\n //4.设置内容处理器\n reader.setContentHandler(new ListHandler());\n //5.读取文档内容 解析xml文档 解析一点就调用处理器处理 所以要先设置内容处理器\n reader.parse(\"src/book.xml\");\n }\n\n}\n\n//内容处理器 得到xml文档所有内容\nclass ListHandler implements ContentHandler{\n //实现接口中的方法\n @Override\n public void startElement(String uri, String localName, String qName,\n Attributes atts) throws SAXException {//当得到开始标签时 调用这个方法\n // TODO Auto-generated method stub\n System.out.print(\"<\" + qName );//打印\n\n //获取属性 还要判断属性有没有(是否为null)\n for (int i = 0; atts != null && i < atts.getLength(); i++) {\n String att_Name = atts.getQName(i);\n String att_Vlaue = atts.getValue(i);\n System.out.print(att_Name + \"=\" + att_Vlaue);\n }\n\n System.out.println( \">\");\n }\n\n @Override\n public void characters(char[] ch, int start, int length)\n throws SAXException { //当解析到标签中内容时就调用这个方法\n // TODO Auto-generated method stub\n System.out.println(new String(ch, start, length));//String解码(要解码的字符集,要解码的第一个byte位置,解码的长度)\n }\n\n @Override\n public void endElement(String uri, String localName, String qName)\n throws SAXException { //当解析到结束标签时 调用这个方法\n // TODO Auto-generated method stub\n System.out.println(\"\");\n }\n\n\n @Override\n public void setDocumentLocator(Locator locator) {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void startDocument() throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void endDocument() throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void startPrefixMapping(String prefix, String uri)\n throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void endPrefixMapping(String prefix) throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void ignorableWhitespace(char[] ch, int start, int length)\n throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void processingInstruction(String target, String data)\n throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n @Override\n public void skippedEntity(String name) throws SAXException {\n // TODO Auto-generated method stub\n\n }\n\n}\n```\n\n**遍历指定标签**\n\n```java\n //打印指定标签的值(这里指定第二个作者的值)\npackage cn.sax;\n\nimport java.io.IOException;\n\nimport javax.xml.parsers.ParserConfigurationException;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\n\nimport org.junit.Test;\nimport org.xml.sax.Attributes;\nimport org.xml.sax.SAXException;\nimport org.xml.sax.XMLReader;\nimport org.xml.sax.helpers.DefaultHandler;\n\n\npublic class sax解析获得指定标签的值{\n @Test\n public void main() throws ParserConfigurationException, SAXException, IOException {\n //1.创建解析工厂 抽象工厂\n SAXParserFactory factory = SAXParserFactory.newInstance();\n //2.得到解析器\n SAXParser parser = factory.newSAXParser();\n //3.得到读取器\n XMLReader reader = parser.getXMLReader();\n //4.设置内容处理器\n reader.setContentHandler(new TagValueHandler());\n //5.读取文档内容 解析xml文档\n reader.parse(\"src/book.xml\");\n }\n}\n\n//获取指定标签的值\nclass TagValueHandler extends DefaultHandler{\n private String currentTag;//用来记住当前解析到的是什么标签\n private int needNumber = 2; //需要的标签是第几个标签\n private int currentNumber; //当前解析到的是第几个标签\n\n @Override\n public void startElement(String uri, String localName, String qName,\n Attributes attributes) throws SAXException {\n // TODO Auto-generated method stub\n super.startElement(uri, localName, qName, attributes);//可以不写\n\n currentTag = qName;\n if(currentTag.equals(\"作者\")){\n currentNumber++;\n }\n }\n\n @Override\n public void endElement(String uri, String localName, String qName)\n throws SAXException {\n // TODO Auto-generated method stub\n super.endElement(uri, localName, qName);\n\n currentTag = null;//置空\n }\n\n @Override\n public void characters(char[] ch, int start, int length)\n throws SAXException {\n // TODO Auto-generated method stub\n super.characters(ch, start, length);\n\n if(\"作者\".equals(currentTag) && currentNumber==needNumber){ \n System.out.println(new String(ch, start, length));\n }\n }\n\n}\n```\n","slug":"2017_sax","published":1,"updated":"2018-01-13T02:29:22.259Z","layout":"post","photos":[],"link":"","_id":"ckm3inuws003v24ujesf6twv5","content":"

这是示例被解析xml

\n
<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<书架>\n    <书>\n        <书名>java实战</书名>\n        <作者>张三</作者>\n        <售价>121元</售价>\n        <售价>12元</售价>\n    </书>\n    <书>\n        <书名 color="yellow" name="XXX">c测试</书名>\n        <作者>李四</作者>\n        <售价 color="rrr">54元</售价>\n        <售价>12元</售价></书>\n</书架>\n
\n

遍历全部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

package cn.sax;

import java.io.IOException;

import javax.sql.rowset.spi.XmlReader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

public class sax解析xml {
@Test
public void main() throws ParserConfigurationException, SAXException, IOException {
//1.创建解析工厂 抽象工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser parser = factory.newSAXParser();
//3.得到读取器
XMLReader reader = parser.getXMLReader();
//4.设置内容处理器
reader.setContentHandler(new ListHandler());
//5.读取文档内容 解析xml文档 解析一点就调用处理器处理 所以要先设置内容处理器
reader.parse(\"src/book.xml\");
}

}

//内容处理器 得到xml文档所有内容
class ListHandler implements ContentHandler{
//实现接口中的方法
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {//当得到开始标签时 调用这个方法
// TODO Auto-generated method stub
System.out.print(\"<\" + qName );//打印

//获取属性 还要判断属性有没有(是否为null)
for (int i = 0; atts != null && i < atts.getLength(); i++) {
String att_Name = atts.getQName(i);
String att_Vlaue = atts.getValue(i);
System.out.print(att_Name + \"=\" + att_Vlaue);
}

System.out.println( \">\");
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException { //当解析到标签中内容时就调用这个方法
// TODO Auto-generated method stub
System.out.println(new String(ch, start, length));//String解码(要解码的字符集,要解码的第一个byte位置,解码的长度)
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException { //当解析到结束标签时 调用这个方法
// TODO Auto-generated method stub
System.out.println(\"</\"+ qName +\">\");
}


@Override
public void setDocumentLocator(Locator locator) {
// TODO Auto-generated method stub

}

@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void endPrefixMapping(String prefix) throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void processingInstruction(String target, String data)
throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void skippedEntity(String name) throws SAXException {
// TODO Auto-generated method stub

}

}

\n

遍历指定标签

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 //打印指定标签的值(这里指定第二个作者的值)
package cn.sax;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;


public class sax解析获得指定标签的值{
@Test
public void main() throws ParserConfigurationException, SAXException, IOException {
//1.创建解析工厂 抽象工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser parser = factory.newSAXParser();
//3.得到读取器
XMLReader reader = parser.getXMLReader();
//4.设置内容处理器
reader.setContentHandler(new TagValueHandler());
//5.读取文档内容 解析xml文档
reader.parse(\"src/book.xml\");
}
}

//获取指定标签的值
class TagValueHandler extends DefaultHandler{
private String currentTag;//用来记住当前解析到的是什么标签
private int needNumber = 2; //需要的标签是第几个标签
private int currentNumber; //当前解析到的是第几个标签

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
super.startElement(uri, localName, qName, attributes);//可以不写

currentTag = qName;
if(currentTag.equals(\"作者\")){
currentNumber++;
}
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
super.endElement(uri, localName, qName);

currentTag = null;//置空
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub
super.characters(ch, start, length);

if(\"作者\".equals(currentTag) && currentNumber==needNumber){
System.out.println(new String(ch, start, length));
}
}

}
\n","site":{"data":{}},"excerpt":"

这是示例被解析xml

\n
<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<书架>\n    <书>\n        <书名>java实战</书名>\n        <作者>张三</作者>\n        <售价>121元</售价>\n        <售价>12元</售价>\n    </书>\n    <书>\n        <书名 color="yellow" name="XXX">c测试</书名>\n        <作者>李四</作者>\n        <售价 color="rrr">54元</售价>\n        <售价>12元</售价></书>\n</书架>\n
","more":"

遍历全部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

package cn.sax;

import java.io.IOException;

import javax.sql.rowset.spi.XmlReader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

public class sax解析xml {
@Test
public void main() throws ParserConfigurationException, SAXException, IOException {
//1.创建解析工厂 抽象工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser parser = factory.newSAXParser();
//3.得到读取器
XMLReader reader = parser.getXMLReader();
//4.设置内容处理器
reader.setContentHandler(new ListHandler());
//5.读取文档内容 解析xml文档 解析一点就调用处理器处理 所以要先设置内容处理器
reader.parse(\"src/book.xml\");
}

}

//内容处理器 得到xml文档所有内容
class ListHandler implements ContentHandler{
//实现接口中的方法
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {//当得到开始标签时 调用这个方法
// TODO Auto-generated method stub
System.out.print(\"<\" + qName );//打印

//获取属性 还要判断属性有没有(是否为null)
for (int i = 0; atts != null && i < atts.getLength(); i++) {
String att_Name = atts.getQName(i);
String att_Vlaue = atts.getValue(i);
System.out.print(att_Name + \"=\" + att_Vlaue);
}

System.out.println( \">\");
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException { //当解析到标签中内容时就调用这个方法
// TODO Auto-generated method stub
System.out.println(new String(ch, start, length));//String解码(要解码的字符集,要解码的第一个byte位置,解码的长度)
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException { //当解析到结束标签时 调用这个方法
// TODO Auto-generated method stub
System.out.println(\"</\"+ qName +\">\");
}


@Override
public void setDocumentLocator(Locator locator) {
// TODO Auto-generated method stub

}

@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void endPrefixMapping(String prefix) throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void processingInstruction(String target, String data)
throws SAXException {
// TODO Auto-generated method stub

}

@Override
public void skippedEntity(String name) throws SAXException {
// TODO Auto-generated method stub

}

}

\n

遍历指定标签

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 //打印指定标签的值(这里指定第二个作者的值)
package cn.sax;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.junit.Test;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;


public class sax解析获得指定标签的值{
@Test
public void main() throws ParserConfigurationException, SAXException, IOException {
//1.创建解析工厂 抽象工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.得到解析器
SAXParser parser = factory.newSAXParser();
//3.得到读取器
XMLReader reader = parser.getXMLReader();
//4.设置内容处理器
reader.setContentHandler(new TagValueHandler());
//5.读取文档内容 解析xml文档
reader.parse(\"src/book.xml\");
}
}

//获取指定标签的值
class TagValueHandler extends DefaultHandler{
private String currentTag;//用来记住当前解析到的是什么标签
private int needNumber = 2; //需要的标签是第几个标签
private int currentNumber; //当前解析到的是第几个标签

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
super.startElement(uri, localName, qName, attributes);//可以不写

currentTag = qName;
if(currentTag.equals(\"作者\")){
currentNumber++;
}
}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
super.endElement(uri, localName, qName);

currentTag = null;//置空
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub
super.characters(ch, start, length);

if(\"作者\".equals(currentTag) && currentNumber==needNumber){
System.out.println(new String(ch, start, length));
}
}

}
"},{"title":"scikit-learn数据预处理fit_transform()与transform()的区别(转)","comments":1,"description":null,"toc":true,"_content":"\n### 问题:\nscikit-learn中fit_transform()与transform()到底有什么区别,能不能混用?\n\n\n\n* 二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)\n* fit_transform(partData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该partData进行转换transform,从而实现数据的标准化、归一化等等。。\n* 根据对之前部分fit的整体指标,对剩余的数据(restData)使用同样的均值、方差、最大最小值等指标进行转换transform(restData),从而保证part、rest处理方式相同。\n* 必须先用fit_transform(partData),之后再transform(restData)\n* 如果直接transform(partData),程序会报错\n* 如果fit_transfrom(partData)后,使用fit_transform(restData)而不用transform(restData),虽然也能归一化,但是两个结果不是在同一个“标准”下的,具有明显差异。\n\n### 实验:\n使用preprocessing.MinMaxScaler()对象对数据进行归一化。原理是:(x-xMin)/(xMax - xMin),从而将所有数据映射到【0,1】区间。\n\n```python\nimport numpy as np \n\nfrom sklearn.preprocessing import MinMaxScaler\n\ndata = np.array(np.random.randint(-100,100,24).reshape(6,4))\n\ndata\nOut[55]: \narray([[ 68, -63, -31, -10],\n [ 49, -49, 73, 18],\n [ 46, 65, 75, -78],\n [-72, 30, 90, -80],\n [ 95, -88, 79, -49],\n [ 34, -81, 57, 83]])\n\ntrain = data[:4]\n\ntest = data[4:]\n\ntrain\nOut[58]: \narray([[ 68, -63, -31, -10],\n [ 49, -49, 73, 18],\n [ 46, 65, 75, -78],\n [-72, 30, 90, -80]])\n\ntest\nOut[59]: \narray([[ 95, -88, 79, -49],\n [ 34, -81, 57, 83]])\n\nminmaxTransformer = MinMaxScaler(feature_range=(0,1))\n\n#先对train用fit_transformer(),包括拟合fit找到xMin,xMax,再transform归一化\ntrain_transformer = minmaxTransformer.fit_transform(train)\n\n#根据train集合的xMin,xMax,对test集合进行归一化transform.\n#(如果test中的某个值比之前的xMin还要小,依然用原来的xMin;同理如果test中的某个值比之前的xMax还要大,依然用原来的xMax.\n#所以,对test集合用同样的xMin和xMax,**有可能不再映射到【0,1】**)\ntest_transformer = minmaxTransformer.transform(test)\n\ntrain_transformer\nOut[64]: \narray([[ 1. , 0. , 0. , 0.71428571],\n [ 0.86428571, 0.109375 , 0.85950413, 1. ],\n [ 0.84285714, 1. , 0.87603306, 0.02040816],\n [ 0. , 0.7265625 , 1. , 0. ]])\n\ntest_transformer\nOut[65]: \narray([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],\n [ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])\n\n#如果少了fit环节,直接transform(partData),则会报错\n\nminmaxTransformer = MinMaxScaler(feature_range=(0,1))\n\ntrain_transformer2 = minmaxTransformer.transform(train)\nTraceback (most recent call last):\n\n File \"\", line 1, in \n train_transformer2 = minmaxTransformer.transform(train)\n\n File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\preprocessing\\data.py\", line 352, in transform\n check_is_fitted(self, 'scale_')\n\n File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\utils\\validation.py\", line 690, in check_is_fitted\n raise _NotFittedError(msg % {'name': type(estimator).__name__})\n\nNotFittedError: This MinMaxScaler instance is not fitted yet. Call 'fit' with appropriate arguments before using this method.\n\n#如果对test也用fit_transform(),则结果跟之前不一样。对于许多机器学习算法来说,对于train和test的处理应该统一。\n\ntest_transformer2 = minmaxTransformer.fit_transform(test)\n\ntest_transformer2\nOut[71]: \narray([[ 1., 0., 1., 0.],\n [ 0., 1., 0., 1.]])\n\ntest_transformer\nOut[72]: \narray([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],\n [ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])\n\n```\n\n转载来自[这里](http://blog.csdn.net/anecdotegyb/article/details/74857055)\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/2018031201.md","raw":"---\ntitle: scikit-learn数据预处理fit_transform()与transform()的区别(转)\n\ncomments: true \n\ntags: \n - python\n - 机器学习\n\ncategories: \n - python\n\ndescription: \n\ntoc: true\n \n---\n\n### 问题:\nscikit-learn中fit_transform()与transform()到底有什么区别,能不能混用?\n\n\n\n* 二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)\n* fit_transform(partData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该partData进行转换transform,从而实现数据的标准化、归一化等等。。\n* 根据对之前部分fit的整体指标,对剩余的数据(restData)使用同样的均值、方差、最大最小值等指标进行转换transform(restData),从而保证part、rest处理方式相同。\n* 必须先用fit_transform(partData),之后再transform(restData)\n* 如果直接transform(partData),程序会报错\n* 如果fit_transfrom(partData)后,使用fit_transform(restData)而不用transform(restData),虽然也能归一化,但是两个结果不是在同一个“标准”下的,具有明显差异。\n\n### 实验:\n使用preprocessing.MinMaxScaler()对象对数据进行归一化。原理是:(x-xMin)/(xMax - xMin),从而将所有数据映射到【0,1】区间。\n\n```python\nimport numpy as np \n\nfrom sklearn.preprocessing import MinMaxScaler\n\ndata = np.array(np.random.randint(-100,100,24).reshape(6,4))\n\ndata\nOut[55]: \narray([[ 68, -63, -31, -10],\n [ 49, -49, 73, 18],\n [ 46, 65, 75, -78],\n [-72, 30, 90, -80],\n [ 95, -88, 79, -49],\n [ 34, -81, 57, 83]])\n\ntrain = data[:4]\n\ntest = data[4:]\n\ntrain\nOut[58]: \narray([[ 68, -63, -31, -10],\n [ 49, -49, 73, 18],\n [ 46, 65, 75, -78],\n [-72, 30, 90, -80]])\n\ntest\nOut[59]: \narray([[ 95, -88, 79, -49],\n [ 34, -81, 57, 83]])\n\nminmaxTransformer = MinMaxScaler(feature_range=(0,1))\n\n#先对train用fit_transformer(),包括拟合fit找到xMin,xMax,再transform归一化\ntrain_transformer = minmaxTransformer.fit_transform(train)\n\n#根据train集合的xMin,xMax,对test集合进行归一化transform.\n#(如果test中的某个值比之前的xMin还要小,依然用原来的xMin;同理如果test中的某个值比之前的xMax还要大,依然用原来的xMax.\n#所以,对test集合用同样的xMin和xMax,**有可能不再映射到【0,1】**)\ntest_transformer = minmaxTransformer.transform(test)\n\ntrain_transformer\nOut[64]: \narray([[ 1. , 0. , 0. , 0.71428571],\n [ 0.86428571, 0.109375 , 0.85950413, 1. ],\n [ 0.84285714, 1. , 0.87603306, 0.02040816],\n [ 0. , 0.7265625 , 1. , 0. ]])\n\ntest_transformer\nOut[65]: \narray([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],\n [ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])\n\n#如果少了fit环节,直接transform(partData),则会报错\n\nminmaxTransformer = MinMaxScaler(feature_range=(0,1))\n\ntrain_transformer2 = minmaxTransformer.transform(train)\nTraceback (most recent call last):\n\n File \"\", line 1, in \n train_transformer2 = minmaxTransformer.transform(train)\n\n File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\preprocessing\\data.py\", line 352, in transform\n check_is_fitted(self, 'scale_')\n\n File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\utils\\validation.py\", line 690, in check_is_fitted\n raise _NotFittedError(msg % {'name': type(estimator).__name__})\n\nNotFittedError: This MinMaxScaler instance is not fitted yet. Call 'fit' with appropriate arguments before using this method.\n\n#如果对test也用fit_transform(),则结果跟之前不一样。对于许多机器学习算法来说,对于train和test的处理应该统一。\n\ntest_transformer2 = minmaxTransformer.fit_transform(test)\n\ntest_transformer2\nOut[71]: \narray([[ 1., 0., 1., 0.],\n [ 0., 1., 0., 1.]])\n\ntest_transformer\nOut[72]: \narray([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],\n [ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])\n\n```\n\n转载来自[这里](http://blog.csdn.net/anecdotegyb/article/details/74857055)\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"2018031201","published":1,"date":"2020-01-15T05:50:39.654Z","updated":"2021-03-10T13:50:15.349Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwu004024uj1pvi13cg","content":"

问题:

scikit-learn中fit_transform()与transform()到底有什么区别,能不能混用?

\n\n
    \n
  • 二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)
  • \n
  • fit_transform(partData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该partData进行转换transform,从而实现数据的标准化、归一化等等。。
  • \n
  • 根据对之前部分fit的整体指标,对剩余的数据(restData)使用同样的均值、方差、最大最小值等指标进行转换transform(restData),从而保证part、rest处理方式相同。
  • \n
  • 必须先用fit_transform(partData),之后再transform(restData)
  • \n
  • 如果直接transform(partData),程序会报错
  • \n
  • 如果fit_transfrom(partData)后,使用fit_transform(restData)而不用transform(restData),虽然也能归一化,但是两个结果不是在同一个“标准”下的,具有明显差异。
  • \n
\n

实验:

使用preprocessing.MinMaxScaler()对象对数据进行归一化。原理是:(x-xMin)/(xMax - xMin),从而将所有数据映射到【0,1】区间。

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import numpy as np 

from sklearn.preprocessing import MinMaxScaler

data = np.array(np.random.randint(-100,100,24).reshape(6,4))

data
Out[55]:
array([[ 68, -63, -31, -10],
[ 49, -49, 73, 18],
[ 46, 65, 75, -78],
[-72, 30, 90, -80],
[ 95, -88, 79, -49],
[ 34, -81, 57, 83]])

train = data[:4]

test = data[4:]

train
Out[58]:
array([[ 68, -63, -31, -10],
[ 49, -49, 73, 18],
[ 46, 65, 75, -78],
[-72, 30, 90, -80]])

test
Out[59]:
array([[ 95, -88, 79, -49],
[ 34, -81, 57, 83]])

minmaxTransformer = MinMaxScaler(feature_range=(0,1))

#先对train用fit_transformer(),包括拟合fit找到xMin,xMax,再transform归一化
train_transformer = minmaxTransformer.fit_transform(train)

#根据train集合的xMin,xMax,对test集合进行归一化transform.
#(如果test中的某个值比之前的xMin还要小,依然用原来的xMin;同理如果test中的某个值比之前的xMax还要大,依然用原来的xMax.
#所以,对test集合用同样的xMin和xMax,**有可能不再映射到【0,1】**)
test_transformer = minmaxTransformer.transform(test)

train_transformer
Out[64]:
array([[ 1. , 0. , 0. , 0.71428571],
[ 0.86428571, 0.109375 , 0.85950413, 1. ],
[ 0.84285714, 1. , 0.87603306, 0.02040816],
[ 0. , 0.7265625 , 1. , 0. ]])

test_transformer
Out[65]:
array([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],
[ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])

#如果少了fit环节,直接transform(partData),则会报错

minmaxTransformer = MinMaxScaler(feature_range=(0,1))

train_transformer2 = minmaxTransformer.transform(train)
Traceback (most recent call last):

File \"<ipython-input-68-a2aeaf2132be>\", line 1, in <module>
train_transformer2 = minmaxTransformer.transform(train)

File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\preprocessing\\data.py\", line 352, in transform
check_is_fitted(self, 'scale_')

File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\utils\\validation.py\", line 690, in check_is_fitted
raise _NotFittedError(msg % {'name': type(estimator).__name__})

NotFittedError: This MinMaxScaler instance is not fitted yet. Call 'fit' with appropriate arguments before using this method.

#如果对test也用fit_transform(),则结果跟之前不一样。对于许多机器学习算法来说,对于train和test的处理应该统一。

test_transformer2 = minmaxTransformer.fit_transform(test)

test_transformer2
Out[71]:
array([[ 1., 0., 1., 0.],
[ 0., 1., 0., 1.]])

test_transformer
Out[72]:
array([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],
[ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])
\n

转载来自这里

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"

问题:

scikit-learn中fit_transform()与transform()到底有什么区别,能不能混用?

","more":"
    \n
  • 二者的功能都是对数据进行某种统一处理(比如标准化~N(0,1),将数据缩放(映射)到某个固定区间,归一化,正则化等)
  • \n
  • fit_transform(partData)对部分数据先拟合fit,找到该part的整体指标,如均值、方差、最大值最小值等等(根据具体转换的目的),然后对该partData进行转换transform,从而实现数据的标准化、归一化等等。。
  • \n
  • 根据对之前部分fit的整体指标,对剩余的数据(restData)使用同样的均值、方差、最大最小值等指标进行转换transform(restData),从而保证part、rest处理方式相同。
  • \n
  • 必须先用fit_transform(partData),之后再transform(restData)
  • \n
  • 如果直接transform(partData),程序会报错
  • \n
  • 如果fit_transfrom(partData)后,使用fit_transform(restData)而不用transform(restData),虽然也能归一化,但是两个结果不是在同一个“标准”下的,具有明显差异。
  • \n
\n

实验:

使用preprocessing.MinMaxScaler()对象对数据进行归一化。原理是:(x-xMin)/(xMax - xMin),从而将所有数据映射到【0,1】区间。

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import numpy as np 

from sklearn.preprocessing import MinMaxScaler

data = np.array(np.random.randint(-100,100,24).reshape(6,4))

data
Out[55]:
array([[ 68, -63, -31, -10],
[ 49, -49, 73, 18],
[ 46, 65, 75, -78],
[-72, 30, 90, -80],
[ 95, -88, 79, -49],
[ 34, -81, 57, 83]])

train = data[:4]

test = data[4:]

train
Out[58]:
array([[ 68, -63, -31, -10],
[ 49, -49, 73, 18],
[ 46, 65, 75, -78],
[-72, 30, 90, -80]])

test
Out[59]:
array([[ 95, -88, 79, -49],
[ 34, -81, 57, 83]])

minmaxTransformer = MinMaxScaler(feature_range=(0,1))

#先对train用fit_transformer(),包括拟合fit找到xMin,xMax,再transform归一化
train_transformer = minmaxTransformer.fit_transform(train)

#根据train集合的xMin,xMax,对test集合进行归一化transform.
#(如果test中的某个值比之前的xMin还要小,依然用原来的xMin;同理如果test中的某个值比之前的xMax还要大,依然用原来的xMax.
#所以,对test集合用同样的xMin和xMax,**有可能不再映射到【0,1】**)
test_transformer = minmaxTransformer.transform(test)

train_transformer
Out[64]:
array([[ 1. , 0. , 0. , 0.71428571],
[ 0.86428571, 0.109375 , 0.85950413, 1. ],
[ 0.84285714, 1. , 0.87603306, 0.02040816],
[ 0. , 0.7265625 , 1. , 0. ]])

test_transformer
Out[65]:
array([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],
[ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])

#如果少了fit环节,直接transform(partData),则会报错

minmaxTransformer = MinMaxScaler(feature_range=(0,1))

train_transformer2 = minmaxTransformer.transform(train)
Traceback (most recent call last):

File \"<ipython-input-68-a2aeaf2132be>\", line 1, in <module>
train_transformer2 = minmaxTransformer.transform(train)

File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\preprocessing\\data.py\", line 352, in transform
check_is_fitted(self, 'scale_')

File \"D:\\Program Files\\Anaconda3\\lib\\site-packages\\sklearn\\utils\\validation.py\", line 690, in check_is_fitted
raise _NotFittedError(msg % {'name': type(estimator).__name__})

NotFittedError: This MinMaxScaler instance is not fitted yet. Call 'fit' with appropriate arguments before using this method.

#如果对test也用fit_transform(),则结果跟之前不一样。对于许多机器学习算法来说,对于train和test的处理应该统一。

test_transformer2 = minmaxTransformer.fit_transform(test)

test_transformer2
Out[71]:
array([[ 1., 0., 1., 0.],
[ 0., 1., 0., 1.]])

test_transformer
Out[72]:
array([[ 1.19285714, -0.1953125 , 0.90909091, 0.31632653],
[ 0.75714286, -0.140625 , 0.72727273, 1.66326531]])
\n

转载来自这里

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"关于使用sklearn进行数据预处理-归一化/标准化/正则化(转)","comments":1,"description":null,"toc":true,"_content":"\n## 一、标准化(Z-Score),或者去除均值和方差缩放\n公式为:(X-mean)/std 计算时对每个属性/每列分别进行。\n\n将数据按期属性(按列进行)减去其均值,并处以其方差。得到的结果是,对于每个属性/每列来说所有数据都聚集在0附近,方差为1。\n\n实现时,有两种不同的方式:\n\n* 使用sklearn.preprocessing.scale()函数,可以直接将给定数据进行标准化。\n\n```python\n>>> from sklearn import preprocessing\n>>> import numpy as np\n>>> X = np.array([[ 1., -1., 2.],\n... [ 2., 0., 0.],\n... [ 0., 1., -1.]])\n>>> X_scaled = preprocessing.scale(X)\n \n>>> X_scaled \narray([[ 0. ..., -1.22..., 1.33...],\n [ 1.22..., 0. ..., -0.26...],\n [-1.22..., 1.22..., -1.06...]])\n \n>>>#处理后数据的均值和方差\n>>> X_scaled.mean(axis=0)\narray([ 0., 0., 0.])\n \n>>> X_scaled.std(axis=0)\narray([ 1., 1., 1.])\n```\n\n* 使用sklearn.preprocessing.StandardScaler类,使用该类的好处在于可以保存训练集中的参数(均值、方差)直接使用其对象转换测试集数据。\n```python\n>>> scaler = preprocessing.StandardScaler().fit(X)\n>>> scaler\nStandardScaler(copy=True, with_mean=True, with_std=True)\n \n>>> scaler.mean_ \narray([ 1. ..., 0. ..., 0.33...])\n \n>>> scaler.std_ \narray([ 0.81..., 0.81..., 1.24...])\n \n>>> scaler.transform(X) \narray([[ 0. ..., -1.22..., 1.33...],\n [ 1.22..., 0. ..., -0.26...],\n [-1.22..., 1.22..., -1.06...]])\n \n \n>>>#可以直接使用训练集对测试集数据进行转换\n>>> scaler.transform([[-1., 1., 0.]]) \narray([[-2.44..., 1.22..., -0.26...]])\n```\n\n## 二、将属性缩放到一个指定范围\n\n除了上述介绍的方法之外,另一种常用的方法是将属性缩放到一个指定的最大和最小值(通常是1-0)之间,这可以通过preprocessing.MinMaxScaler类实现。\n\n使用这种方法的目的包括:\n\n1. 对于方差非常小的属性可以增强其稳定性。\n\n2. 维持稀疏矩阵中为0的条目\n\n```python\n>>> X_train = np.array([[ 1., -1., 2.],\n... [ 2., 0., 0.],\n... [ 0., 1., -1.]])\n...\n>>> min_max_scaler = preprocessing.MinMaxScaler()\n>>> X_train_minmax = min_max_scaler.fit_transform(X_train)\n>>> X_train_minmax\narray([[ 0.5 , 0. , 1. ],\n [ 1. , 0.5 , 0.33333333],\n [ 0. , 1. , 0. ]])\n \n>>> #将相同的缩放应用到测试集数据中\n>>> X_test = np.array([[ -3., -1., 4.]])\n>>> X_test_minmax = min_max_scaler.transform(X_test)\n>>> X_test_minmax\narray([[-1.5 , 0. , 1.66666667]])\n \n \n>>> #缩放因子等属性\n>>> min_max_scaler.scale_ \narray([ 0.5 , 0.5 , 0.33...])\n \n>>> min_max_scaler.min_ \narray([ 0. , 0.5 , 0.33...])\n```\n\n当然,在构造类对象的时候也可以直接指定最大最小值的范围:feature_range=(min, max),此时应用的公式变为:\n\nX_std=(X-X.min(axis=0))/(X.max(axis=0)-X.min(axis=0))\n\nX_scaled=X_std/(max-min)+min\n\n## 三、正则化(Normalization)\n\n正则化的过程是将每个样本缩放到单位范数(每个样本的范数为1),如果后面要使用如二次型(点积)或者其它核方法计算两个样本之间的相似性这个方法会很有用。\n\nNormalization主要思想是对每个样本计算其p-范数,然后对该样本中每个元素除以该范数,这样处理的结果是使得每个处理后样本的p-范数(l1-norm,l2-norm)等于1。\n\n p-范数的计算公式:||X||p=(|x1|^p+|x2|^p+...+|xn|^p)^1/p\n该方法主要应用于文本分类和聚类中。例如,对于两个TF-IDF向量的l2-norm进行点积,就可以得到这两个向量的余弦相似性。\n\n1. 可以使用preprocessing.normalize()函数对指定数据进行转换:\n\n```python\n>>> X = [[ 1., -1., 2.],\n... [ 2., 0., 0.],\n... [ 0., 1., -1.]]\n>>> X_normalized = preprocessing.normalize(X, norm='l2')\n \n>>> X_normalized \narray([[ 0.40..., -0.40..., 0.81...],\n [ 1. ..., 0. ..., 0. ...],\n [ 0. ..., 0.70..., -0.70...]])\n```\n2. 可以使用processing.Normalizer()类实现对训练集和测试集的拟合和转换:\n\n```python\n>>> normalizer = preprocessing.Normalizer().fit(X) # fit does nothing\n>>> normalizer\nNormalizer(copy=True, norm='l2')\n \n>>>\n>>> normalizer.transform(X) \narray([[ 0.40..., -0.40..., 0.81...],\n [ 1. ..., 0. ..., 0. ...],\n [ 0. ..., 0.70..., -0.70...]])\n \n>>> normalizer.transform([[-1., 1., 0.]]) \narray([[-0.70..., 0.70..., 0. ...]])\n```\n\n*补充:*\n\n{% qnimg 091414004623860.png title:如图 alt:图片说明 extend:?imageView2/2/w/800 %}\n\n\n\n转载来自[这里](https://www.cnblogs.com/chaosimple/p/4153167.html)\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/2018031202.md","raw":"---\ntitle: 关于使用sklearn进行数据预处理-归一化/标准化/正则化(转)\n\ncomments: true \n\ntags: \n - python\n - 机器学习\n\ncategories: \n - python\n\ndescription: \n\ntoc: true\n \n---\n\n## 一、标准化(Z-Score),或者去除均值和方差缩放\n公式为:(X-mean)/std 计算时对每个属性/每列分别进行。\n\n将数据按期属性(按列进行)减去其均值,并处以其方差。得到的结果是,对于每个属性/每列来说所有数据都聚集在0附近,方差为1。\n\n实现时,有两种不同的方式:\n\n* 使用sklearn.preprocessing.scale()函数,可以直接将给定数据进行标准化。\n\n```python\n>>> from sklearn import preprocessing\n>>> import numpy as np\n>>> X = np.array([[ 1., -1., 2.],\n... [ 2., 0., 0.],\n... [ 0., 1., -1.]])\n>>> X_scaled = preprocessing.scale(X)\n \n>>> X_scaled \narray([[ 0. ..., -1.22..., 1.33...],\n [ 1.22..., 0. ..., -0.26...],\n [-1.22..., 1.22..., -1.06...]])\n \n>>>#处理后数据的均值和方差\n>>> X_scaled.mean(axis=0)\narray([ 0., 0., 0.])\n \n>>> X_scaled.std(axis=0)\narray([ 1., 1., 1.])\n```\n\n* 使用sklearn.preprocessing.StandardScaler类,使用该类的好处在于可以保存训练集中的参数(均值、方差)直接使用其对象转换测试集数据。\n```python\n>>> scaler = preprocessing.StandardScaler().fit(X)\n>>> scaler\nStandardScaler(copy=True, with_mean=True, with_std=True)\n \n>>> scaler.mean_ \narray([ 1. ..., 0. ..., 0.33...])\n \n>>> scaler.std_ \narray([ 0.81..., 0.81..., 1.24...])\n \n>>> scaler.transform(X) \narray([[ 0. ..., -1.22..., 1.33...],\n [ 1.22..., 0. ..., -0.26...],\n [-1.22..., 1.22..., -1.06...]])\n \n \n>>>#可以直接使用训练集对测试集数据进行转换\n>>> scaler.transform([[-1., 1., 0.]]) \narray([[-2.44..., 1.22..., -0.26...]])\n```\n\n## 二、将属性缩放到一个指定范围\n\n除了上述介绍的方法之外,另一种常用的方法是将属性缩放到一个指定的最大和最小值(通常是1-0)之间,这可以通过preprocessing.MinMaxScaler类实现。\n\n使用这种方法的目的包括:\n\n1. 对于方差非常小的属性可以增强其稳定性。\n\n2. 维持稀疏矩阵中为0的条目\n\n```python\n>>> X_train = np.array([[ 1., -1., 2.],\n... [ 2., 0., 0.],\n... [ 0., 1., -1.]])\n...\n>>> min_max_scaler = preprocessing.MinMaxScaler()\n>>> X_train_minmax = min_max_scaler.fit_transform(X_train)\n>>> X_train_minmax\narray([[ 0.5 , 0. , 1. ],\n [ 1. , 0.5 , 0.33333333],\n [ 0. , 1. , 0. ]])\n \n>>> #将相同的缩放应用到测试集数据中\n>>> X_test = np.array([[ -3., -1., 4.]])\n>>> X_test_minmax = min_max_scaler.transform(X_test)\n>>> X_test_minmax\narray([[-1.5 , 0. , 1.66666667]])\n \n \n>>> #缩放因子等属性\n>>> min_max_scaler.scale_ \narray([ 0.5 , 0.5 , 0.33...])\n \n>>> min_max_scaler.min_ \narray([ 0. , 0.5 , 0.33...])\n```\n\n当然,在构造类对象的时候也可以直接指定最大最小值的范围:feature_range=(min, max),此时应用的公式变为:\n\nX_std=(X-X.min(axis=0))/(X.max(axis=0)-X.min(axis=0))\n\nX_scaled=X_std/(max-min)+min\n\n## 三、正则化(Normalization)\n\n正则化的过程是将每个样本缩放到单位范数(每个样本的范数为1),如果后面要使用如二次型(点积)或者其它核方法计算两个样本之间的相似性这个方法会很有用。\n\nNormalization主要思想是对每个样本计算其p-范数,然后对该样本中每个元素除以该范数,这样处理的结果是使得每个处理后样本的p-范数(l1-norm,l2-norm)等于1。\n\n p-范数的计算公式:||X||p=(|x1|^p+|x2|^p+...+|xn|^p)^1/p\n该方法主要应用于文本分类和聚类中。例如,对于两个TF-IDF向量的l2-norm进行点积,就可以得到这两个向量的余弦相似性。\n\n1. 可以使用preprocessing.normalize()函数对指定数据进行转换:\n\n```python\n>>> X = [[ 1., -1., 2.],\n... [ 2., 0., 0.],\n... [ 0., 1., -1.]]\n>>> X_normalized = preprocessing.normalize(X, norm='l2')\n \n>>> X_normalized \narray([[ 0.40..., -0.40..., 0.81...],\n [ 1. ..., 0. ..., 0. ...],\n [ 0. ..., 0.70..., -0.70...]])\n```\n2. 可以使用processing.Normalizer()类实现对训练集和测试集的拟合和转换:\n\n```python\n>>> normalizer = preprocessing.Normalizer().fit(X) # fit does nothing\n>>> normalizer\nNormalizer(copy=True, norm='l2')\n \n>>>\n>>> normalizer.transform(X) \narray([[ 0.40..., -0.40..., 0.81...],\n [ 1. ..., 0. ..., 0. ...],\n [ 0. ..., 0.70..., -0.70...]])\n \n>>> normalizer.transform([[-1., 1., 0.]]) \narray([[-0.70..., 0.70..., 0. ...]])\n```\n\n*补充:*\n\n{% qnimg 091414004623860.png title:如图 alt:图片说明 extend:?imageView2/2/w/800 %}\n\n\n\n转载来自[这里](https://www.cnblogs.com/chaosimple/p/4153167.html)\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"2018031202","published":1,"date":"2020-01-15T05:50:39.656Z","updated":"2021-03-10T13:50:15.293Z","layout":"post","photos":[],"link":"","_id":"ckm3inuwv004124ujd5tzoyag","content":"

一、标准化(Z-Score),或者去除均值和方差缩放

公式为:(X-mean)/std 计算时对每个属性/每列分别进行。

\n

将数据按期属性(按列进行)减去其均值,并处以其方差。得到的结果是,对于每个属性/每列来说所有数据都聚集在0附近,方差为1。

\n

实现时,有两种不同的方式:

\n
    \n
  • 使用sklearn.preprocessing.scale()函数,可以直接将给定数据进行标准化。
  • \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> from sklearn import preprocessing
>>> import numpy as np
>>> X = np.array([[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
>>> X_scaled = preprocessing.scale(X)

>>> X_scaled
array([[ 0. ..., -1.22..., 1.33...],
[ 1.22..., 0. ..., -0.26...],
[-1.22..., 1.22..., -1.06...]])

>>>#处理后数据的均值和方差
>>> X_scaled.mean(axis=0)
array([ 0., 0., 0.])

>>> X_scaled.std(axis=0)
array([ 1., 1., 1.])
\n
    \n
  • 使用sklearn.preprocessing.StandardScaler类,使用该类的好处在于可以保存训练集中的参数(均值、方差)直接使用其对象转换测试集数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    >>> scaler = preprocessing.StandardScaler().fit(X)
    >>> scaler
    StandardScaler(copy=True, with_mean=True, with_std=True)

    >>> scaler.mean_
    array([ 1. ..., 0. ..., 0.33...])

    >>> scaler.std_
    array([ 0.81..., 0.81..., 1.24...])

    >>> scaler.transform(X)
    array([[ 0. ..., -1.22..., 1.33...],
    [ 1.22..., 0. ..., -0.26...],
    [-1.22..., 1.22..., -1.06...]])


    >>>#可以直接使用训练集对测试集数据进行转换
    >>> scaler.transform([[-1., 1., 0.]])
    array([[-2.44..., 1.22..., -0.26...]])
    \n
  • \n
\n

二、将属性缩放到一个指定范围

除了上述介绍的方法之外,另一种常用的方法是将属性缩放到一个指定的最大和最小值(通常是1-0)之间,这可以通过preprocessing.MinMaxScaler类实现。

\n

使用这种方法的目的包括:

\n
    \n
  1. 对于方差非常小的属性可以增强其稳定性。

    \n
  2. \n
  3. 维持稀疏矩阵中为0的条目

    \n
  4. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> X_train = np.array([[ 1., -1.,  2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[ 0.5 , 0. , 1. ],
[ 1. , 0.5 , 0.33333333],
[ 0. , 1. , 0. ]])

>>> #将相同的缩放应用到测试集数据中
>>> X_test = np.array([[ -3., -1., 4.]])
>>> X_test_minmax = min_max_scaler.transform(X_test)
>>> X_test_minmax
array([[-1.5 , 0. , 1.66666667]])


>>> #缩放因子等属性
>>> min_max_scaler.scale_
array([ 0.5 , 0.5 , 0.33...])

>>> min_max_scaler.min_
array([ 0. , 0.5 , 0.33...])
\n

当然,在构造类对象的时候也可以直接指定最大最小值的范围:feature_range=(min, max),此时应用的公式变为:

\n

X_std=(X-X.min(axis=0))/(X.max(axis=0)-X.min(axis=0))

\n

X_scaled=X_std/(max-min)+min

\n

三、正则化(Normalization)

正则化的过程是将每个样本缩放到单位范数(每个样本的范数为1),如果后面要使用如二次型(点积)或者其它核方法计算两个样本之间的相似性这个方法会很有用。

\n

Normalization主要思想是对每个样本计算其p-范数,然后对该样本中每个元素除以该范数,这样处理的结果是使得每个处理后样本的p-范数(l1-norm,l2-norm)等于1。

\n
p-范数的计算公式:||X||p=(|x1|^p+|x2|^p+...+|xn|^p)^1/p\n

该方法主要应用于文本分类和聚类中。例如,对于两个TF-IDF向量的l2-norm进行点积,就可以得到这两个向量的余弦相似性。

\n
    \n
  1. 可以使用preprocessing.normalize()函数对指定数据进行转换:
  2. \n
\n
1
2
3
4
5
6
7
8
9
>>> X = [[ 1., -1.,  2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]]
>>> X_normalized = preprocessing.normalize(X, norm='l2')

>>> X_normalized
array([[ 0.40..., -0.40..., 0.81...],
[ 1. ..., 0. ..., 0. ...],
[ 0. ..., 0.70..., -0.70...]])
\n
    \n
  1. 可以使用processing.Normalizer()类实现对训练集和测试集的拟合和转换:
  2. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
>>> normalizer = preprocessing.Normalizer().fit(X)  # fit does nothing
>>> normalizer
Normalizer(copy=True, norm='l2')

>>>
>>> normalizer.transform(X)
array([[ 0.40..., -0.40..., 0.81...],
[ 1. ..., 0. ..., 0. ...],
[ 0. ..., 0.70..., -0.70...]])

>>> normalizer.transform([[-1., 1., 0.]])
array([[-0.70..., 0.70..., 0. ...]])
\n

补充:

\n\"图片说明\"\n

转载来自这里

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"","more":"

一、标准化(Z-Score),或者去除均值和方差缩放

公式为:(X-mean)/std 计算时对每个属性/每列分别进行。

\n

将数据按期属性(按列进行)减去其均值,并处以其方差。得到的结果是,对于每个属性/每列来说所有数据都聚集在0附近,方差为1。

\n

实现时,有两种不同的方式:

\n
    \n
  • 使用sklearn.preprocessing.scale()函数,可以直接将给定数据进行标准化。
  • \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> from sklearn import preprocessing
>>> import numpy as np
>>> X = np.array([[ 1., -1., 2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
>>> X_scaled = preprocessing.scale(X)

>>> X_scaled
array([[ 0. ..., -1.22..., 1.33...],
[ 1.22..., 0. ..., -0.26...],
[-1.22..., 1.22..., -1.06...]])

>>>#处理后数据的均值和方差
>>> X_scaled.mean(axis=0)
array([ 0., 0., 0.])

>>> X_scaled.std(axis=0)
array([ 1., 1., 1.])
\n
    \n
  • 使用sklearn.preprocessing.StandardScaler类,使用该类的好处在于可以保存训练集中的参数(均值、方差)直接使用其对象转换测试集数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    >>> scaler = preprocessing.StandardScaler().fit(X)
    >>> scaler
    StandardScaler(copy=True, with_mean=True, with_std=True)

    >>> scaler.mean_
    array([ 1. ..., 0. ..., 0.33...])

    >>> scaler.std_
    array([ 0.81..., 0.81..., 1.24...])

    >>> scaler.transform(X)
    array([[ 0. ..., -1.22..., 1.33...],
    [ 1.22..., 0. ..., -0.26...],
    [-1.22..., 1.22..., -1.06...]])


    >>>#可以直接使用训练集对测试集数据进行转换
    >>> scaler.transform([[-1., 1., 0.]])
    array([[-2.44..., 1.22..., -0.26...]])
    \n
  • \n
\n

二、将属性缩放到一个指定范围

除了上述介绍的方法之外,另一种常用的方法是将属性缩放到一个指定的最大和最小值(通常是1-0)之间,这可以通过preprocessing.MinMaxScaler类实现。

\n

使用这种方法的目的包括:

\n
    \n
  1. 对于方差非常小的属性可以增强其稳定性。

    \n
  2. \n
  3. 维持稀疏矩阵中为0的条目

    \n
  4. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> X_train = np.array([[ 1., -1.,  2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[ 0.5 , 0. , 1. ],
[ 1. , 0.5 , 0.33333333],
[ 0. , 1. , 0. ]])

>>> #将相同的缩放应用到测试集数据中
>>> X_test = np.array([[ -3., -1., 4.]])
>>> X_test_minmax = min_max_scaler.transform(X_test)
>>> X_test_minmax
array([[-1.5 , 0. , 1.66666667]])


>>> #缩放因子等属性
>>> min_max_scaler.scale_
array([ 0.5 , 0.5 , 0.33...])

>>> min_max_scaler.min_
array([ 0. , 0.5 , 0.33...])
\n

当然,在构造类对象的时候也可以直接指定最大最小值的范围:feature_range=(min, max),此时应用的公式变为:

\n

X_std=(X-X.min(axis=0))/(X.max(axis=0)-X.min(axis=0))

\n

X_scaled=X_std/(max-min)+min

\n

三、正则化(Normalization)

正则化的过程是将每个样本缩放到单位范数(每个样本的范数为1),如果后面要使用如二次型(点积)或者其它核方法计算两个样本之间的相似性这个方法会很有用。

\n

Normalization主要思想是对每个样本计算其p-范数,然后对该样本中每个元素除以该范数,这样处理的结果是使得每个处理后样本的p-范数(l1-norm,l2-norm)等于1。

\n
p-范数的计算公式:||X||p=(|x1|^p+|x2|^p+...+|xn|^p)^1/p\n

该方法主要应用于文本分类和聚类中。例如,对于两个TF-IDF向量的l2-norm进行点积,就可以得到这两个向量的余弦相似性。

\n
    \n
  1. 可以使用preprocessing.normalize()函数对指定数据进行转换:
  2. \n
\n
1
2
3
4
5
6
7
8
9
>>> X = [[ 1., -1.,  2.],
... [ 2., 0., 0.],
... [ 0., 1., -1.]]
>>> X_normalized = preprocessing.normalize(X, norm='l2')

>>> X_normalized
array([[ 0.40..., -0.40..., 0.81...],
[ 1. ..., 0. ..., 0. ...],
[ 0. ..., 0.70..., -0.70...]])
\n
    \n
  1. 可以使用processing.Normalizer()类实现对训练集和测试集的拟合和转换:
  2. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
>>> normalizer = preprocessing.Normalizer().fit(X)  # fit does nothing
>>> normalizer
Normalizer(copy=True, norm='l2')

>>>
>>> normalizer.transform(X)
array([[ 0.40..., -0.40..., 0.81...],
[ 1. ..., 0. ..., 0. ...],
[ 0. ..., 0.70..., -0.70...]])

>>> normalizer.transform([[-1., 1., 0.]])
array([[-0.70..., 0.70..., 0. ...]])
\n

补充:

\n\"图片说明\"\n

转载来自这里

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n"},{"title":"Java元数据与注解的实现原理","comments":1,"description":"学到对象在虚拟机中存储形式的时候了解到,虚拟机内存中有个指针是 对象指向对象类的元数据, 而虚拟机就是通过这个指针确定对象是哪个类的实例, so就了解一下元数据是什么以及元数据在java中的表现方式","_content":"\n*知识来源*: \n>http://blog.csdn.net/vebasan/article/details/4794699\n>http://www.cnblogs.com/liuyonglong/p/3567786.html\n>http://www.oracle.com/technetwork/cn/topics/linux/hunter-meta-097643-zhs.html\n>http://developer.51cto.com/art/200909/152828.htm\n>http://blog.csdn.net/yerenyuan_pku/article/details/52583656\n>http://www.cnblogs.com/lzq2016/p/5169475.html\n\n\n\n#### J2SE 5.0 版本以后新特性 Annotation(注解)\n### 定义: 元数据(MetaData)是数据的数据。元数据是添加到程序元素如方法、字段、类和包上的额外信息。或者说是从信息资源中抽取出来的用于说明其特征、内容的结构化的数据\n >例如:富士苹果有一个属性:它是红色的。假定有一个 FushiApple 类,可以使用 @Color 批注类型的一个批注来指定它的颜色。通过这么做,您就提供了关于苹果的元数据。 \n### 作用: 创建文档,跟踪代码中的依赖性,执行编译时检查,代码分析\n >例如: spring等框架中可以大量运用注解来替代配置文件进行依赖注入(取代了复杂的XML配置文件)\n > 所以说注解其实就是元数据,本质上也是接口,而且是继承了接口Annotation的接口\n >利用元数据来描述资源后,我们就可以用来做很多的事情。比如确定资源,为资源提供检索点,在不同系统之间进行数据交换。\n >比如:我们把所有的控制层都打注解@Controller,就表示此类为控制层,为springMVC提供定位,表明这是控制器,然后在springMVC配置文件中加入用来识别就可以\n \n##元数据的实现\n JDK5.0出来后,java语言中就有了四种类型(TYPE),即类(class)、枚举(enum)、接口(interface)和注解(@interface),它们是处在同一级别的。java就是通过注解来表示元数据的。\n java.lang.annotation.Annotation 本身是接口,而不是注解,当使用关键字@interface 定义一个注解时,该注解隐含的继承了java.lang.annotation.Annotation接口;\n 如果我们定义一个接口,并且让该接口继承自Annotation,并不能作为注解, 定义注解只能依靠@interface实现\n \n \n### JDK提供的基本注解\n1. @SuppressWarnings 压制警告\n\n 参数: \n 1.deprecation :过时的类或方法警告。 \n 2.unchecked:执行了未检查的转换时警告。 \n 3.fallthrough:当Switch程序块直接通往下一种情况而没有Break时的警告。\n 4.path:在类路径、源文件路径等中有不存在的路径时的警告。\n 5.serial:当在可序列化的类上缺少serialVersionUID定义时的警告。\n 6.finally:任何finally子句不能完成时的警告。\n 7.all:关于以上所有情况的警告。\n \n \n2. @Deprecated 设置过时\n3. @Override 表示复写\n\n#### 元注解(注解的注解) 用于修饰一个Annotation的定义\n\n4. @Retention 设置注解的生命周期 \n\n\n RetentionPolicy.SOURCE java源文件 只在源代码级别保留,编译时就会被忽略\n RetentionPolicy.CLASS class文件 编译器将把注解记录在class文件中,当运行Java程序时,JVM会忽略注解。这是默认值。\n RetentionPolicy.RUNTIME 内存的字节码 编译器将把注解记录在class文件中。当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解\n \n \n5. @Target 表示该注解可以用在什么地方\n\n\n 参数:value \n 类型:ElementType ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,... 分别表示注解的不同的使用地方\n\n\n6. @Document 将注解包含在javadoc中 指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档\n\n7. @Inherited\t被它修饰的Annotation将具有继承性 允许子类继承父类的注解 其子类将自动具有该注解\n\n\n## 注解和XML配置文件对比\n\n XML配置文件与代码文件分离,不利于一致性维护,缺乏在运行时的反射机制。而Annotation与代码一起被编译器处理,并能够在运行时访问。 \n 通常XML配置文件都很复杂而且冗长。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。 \n XML配置文件是文本文件,没有显式的类型支持,需要到运行时刻才能发现隐藏的错误。而Annotation是类型安全的,它会被编译器检查。 \n XML文件可以表达复杂的关系,但是在注释中我们却很难表达复杂的或层次的结构。 \n XML配置文件是在代码之外被单独处理的,也就是说基于XML的配置信息不是硬编码的,可以部署的时候进行修改。而修改Annotation则需要进行重新编译,\n 不过我们可以利用AOP提供的机制为已有的代码添加Annotation。通过部署不同的AOP模块,就能使代码具有不同的Annotation,但比起直接修改XML显得复杂。 \n 注释是简单易用的,并且对大多数应用来说已经足够了。而XML文件更复杂,但具有部署的灵活性,因而被用来处理与部署相关的决策。\n 注释与XML配置文件可以一起使用。由于注释只能保存相当少的配置信息,只有预先集成的框架组件(类似在框架组件中已经完成了大多数预备工作)可以广泛地把注释作为配置选项。\n 而XML配置文件作为一个可选的重载机制,可以用于改变注释的默认行为。\n\n\n## 创建注解\n1.定义一个注解类\n\n //@Retention(RetentionPolicy.RUNTIME)\n public @interface MyAnnotation {\n //公共的final静态属性 默认加上public static final 必须初始化\n String user = \"root\";\n String password = \"fangshuoit\";\n \n //公共的抽象方法 默认加上public abstract 调用时必须初始化\n //可以有默认返回值(返回值类型:8种基本类型,String、Class、枚举、注解及这些类型的数组)\n String driverClass() default \"com.mysql.jdbc.Driver\";\n String jdbcUrl() default \"jdbc:mysql://localhost:3306/test\";\n String password() default \"fangshuoit\";\n \n int[] arrayAttr() default {3,4,4};\n }\n\n2.在另一个类或方法上加上此注解,并且添加一个测试方法测试有没有此注解\n\n\n @MyAnnotation(jdbcUrl = \"jdbc:mysql://localhost:3306/test2\")\n public class TestAnnotaion {\n private String name;\n \n @Test\n @MyAnnotation(arrayAttr={2,3,4}, jdbcUrl = \"jdbc:mysql://localhost:3306/test3\")\n public void test() {\n \n }\n \n @SuppressWarnings(\"deprecation\")\n public static void main(String[] args) {\n System.runFinalizersOnExit(true);\n if (TestAnnotaion.class.isAnnotationPresent(MyAnnotation.class)) { // 类上是否有注解,默认情况下返回false\n MyAnnotation annotation = TestAnnotaion.class.getAnnotation(MyAnnotation.class);\n System.out.println(annotation.jdbcUrl()); \n System.out.println(annotation.user);\n }\n System.out.println(\"运行结束\");\n }\n }\n //上面并没有返回System.out.println(annotation.jdbcUrl())结果,\n 要设置生命周期,在注解类上加@Retention(RetentionPolicy.RUNTIME)\n \n 输出: \n jdbc:mysql://localhost:3306/test2\n root\n 运行结束\n \n //表示获取到TestAnnotaion类上的注解@MyAnnotation的信息\n //可以认定@MyAnnotation(driverClass = \"\", jdbcUrl = \"jdbc:mysql://localhost:3306/test2\")是MyAnnotation类的实例对象\n //这里是通过反射获得MyAnnotation对象\n //其实@MyAnnotation()相当于new了一个对象\n //如果数组属性中只有一个元素,这时候属性值部分可以省略大括号。\n \n 可以加上@Target({ElementType.METHOD,ElementType.TYPE})\n 表示只能在方法和类上加此注解\n \n 枚举类型注解属性\n 在注解类中添加 MyEnum season() default MyEnum.Winter;\n 则被注解类可添加 @MyAnnotation(season = MyEnum.Autumn)","source":"_posts/201703092044_annotation.md","raw":"---\ntitle: Java元数据与注解的实现原理\n\ncomments: true \n\ntags: \n - 注解\n - 元数据\n\ncategories: \n - java语言基础\n\ndescription: 学到对象在虚拟机中存储形式的时候了解到,虚拟机内存中有个指针是 对象指向对象类的元数据,\n 而虚拟机就是通过这个指针确定对象是哪个类的实例, so就了解一下元数据是什么以及元数据在java中的表现方式\n\n---\n\n*知识来源*: \n>http://blog.csdn.net/vebasan/article/details/4794699\n>http://www.cnblogs.com/liuyonglong/p/3567786.html\n>http://www.oracle.com/technetwork/cn/topics/linux/hunter-meta-097643-zhs.html\n>http://developer.51cto.com/art/200909/152828.htm\n>http://blog.csdn.net/yerenyuan_pku/article/details/52583656\n>http://www.cnblogs.com/lzq2016/p/5169475.html\n\n\n\n#### J2SE 5.0 版本以后新特性 Annotation(注解)\n### 定义: 元数据(MetaData)是数据的数据。元数据是添加到程序元素如方法、字段、类和包上的额外信息。或者说是从信息资源中抽取出来的用于说明其特征、内容的结构化的数据\n >例如:富士苹果有一个属性:它是红色的。假定有一个 FushiApple 类,可以使用 @Color 批注类型的一个批注来指定它的颜色。通过这么做,您就提供了关于苹果的元数据。 \n### 作用: 创建文档,跟踪代码中的依赖性,执行编译时检查,代码分析\n >例如: spring等框架中可以大量运用注解来替代配置文件进行依赖注入(取代了复杂的XML配置文件)\n > 所以说注解其实就是元数据,本质上也是接口,而且是继承了接口Annotation的接口\n >利用元数据来描述资源后,我们就可以用来做很多的事情。比如确定资源,为资源提供检索点,在不同系统之间进行数据交换。\n >比如:我们把所有的控制层都打注解@Controller,就表示此类为控制层,为springMVC提供定位,表明这是控制器,然后在springMVC配置文件中加入用来识别就可以\n \n##元数据的实现\n JDK5.0出来后,java语言中就有了四种类型(TYPE),即类(class)、枚举(enum)、接口(interface)和注解(@interface),它们是处在同一级别的。java就是通过注解来表示元数据的。\n java.lang.annotation.Annotation 本身是接口,而不是注解,当使用关键字@interface 定义一个注解时,该注解隐含的继承了java.lang.annotation.Annotation接口;\n 如果我们定义一个接口,并且让该接口继承自Annotation,并不能作为注解, 定义注解只能依靠@interface实现\n \n \n### JDK提供的基本注解\n1. @SuppressWarnings 压制警告\n\n 参数: \n 1.deprecation :过时的类或方法警告。 \n 2.unchecked:执行了未检查的转换时警告。 \n 3.fallthrough:当Switch程序块直接通往下一种情况而没有Break时的警告。\n 4.path:在类路径、源文件路径等中有不存在的路径时的警告。\n 5.serial:当在可序列化的类上缺少serialVersionUID定义时的警告。\n 6.finally:任何finally子句不能完成时的警告。\n 7.all:关于以上所有情况的警告。\n \n \n2. @Deprecated 设置过时\n3. @Override 表示复写\n\n#### 元注解(注解的注解) 用于修饰一个Annotation的定义\n\n4. @Retention 设置注解的生命周期 \n\n\n RetentionPolicy.SOURCE java源文件 只在源代码级别保留,编译时就会被忽略\n RetentionPolicy.CLASS class文件 编译器将把注解记录在class文件中,当运行Java程序时,JVM会忽略注解。这是默认值。\n RetentionPolicy.RUNTIME 内存的字节码 编译器将把注解记录在class文件中。当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解\n \n \n5. @Target 表示该注解可以用在什么地方\n\n\n 参数:value \n 类型:ElementType ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,... 分别表示注解的不同的使用地方\n\n\n6. @Document 将注解包含在javadoc中 指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档\n\n7. @Inherited\t被它修饰的Annotation将具有继承性 允许子类继承父类的注解 其子类将自动具有该注解\n\n\n## 注解和XML配置文件对比\n\n XML配置文件与代码文件分离,不利于一致性维护,缺乏在运行时的反射机制。而Annotation与代码一起被编译器处理,并能够在运行时访问。 \n 通常XML配置文件都很复杂而且冗长。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。 \n XML配置文件是文本文件,没有显式的类型支持,需要到运行时刻才能发现隐藏的错误。而Annotation是类型安全的,它会被编译器检查。 \n XML文件可以表达复杂的关系,但是在注释中我们却很难表达复杂的或层次的结构。 \n XML配置文件是在代码之外被单独处理的,也就是说基于XML的配置信息不是硬编码的,可以部署的时候进行修改。而修改Annotation则需要进行重新编译,\n 不过我们可以利用AOP提供的机制为已有的代码添加Annotation。通过部署不同的AOP模块,就能使代码具有不同的Annotation,但比起直接修改XML显得复杂。 \n 注释是简单易用的,并且对大多数应用来说已经足够了。而XML文件更复杂,但具有部署的灵活性,因而被用来处理与部署相关的决策。\n 注释与XML配置文件可以一起使用。由于注释只能保存相当少的配置信息,只有预先集成的框架组件(类似在框架组件中已经完成了大多数预备工作)可以广泛地把注释作为配置选项。\n 而XML配置文件作为一个可选的重载机制,可以用于改变注释的默认行为。\n\n\n## 创建注解\n1.定义一个注解类\n\n //@Retention(RetentionPolicy.RUNTIME)\n public @interface MyAnnotation {\n //公共的final静态属性 默认加上public static final 必须初始化\n String user = \"root\";\n String password = \"fangshuoit\";\n \n //公共的抽象方法 默认加上public abstract 调用时必须初始化\n //可以有默认返回值(返回值类型:8种基本类型,String、Class、枚举、注解及这些类型的数组)\n String driverClass() default \"com.mysql.jdbc.Driver\";\n String jdbcUrl() default \"jdbc:mysql://localhost:3306/test\";\n String password() default \"fangshuoit\";\n \n int[] arrayAttr() default {3,4,4};\n }\n\n2.在另一个类或方法上加上此注解,并且添加一个测试方法测试有没有此注解\n\n\n @MyAnnotation(jdbcUrl = \"jdbc:mysql://localhost:3306/test2\")\n public class TestAnnotaion {\n private String name;\n \n @Test\n @MyAnnotation(arrayAttr={2,3,4}, jdbcUrl = \"jdbc:mysql://localhost:3306/test3\")\n public void test() {\n \n }\n \n @SuppressWarnings(\"deprecation\")\n public static void main(String[] args) {\n System.runFinalizersOnExit(true);\n if (TestAnnotaion.class.isAnnotationPresent(MyAnnotation.class)) { // 类上是否有注解,默认情况下返回false\n MyAnnotation annotation = TestAnnotaion.class.getAnnotation(MyAnnotation.class);\n System.out.println(annotation.jdbcUrl()); \n System.out.println(annotation.user);\n }\n System.out.println(\"运行结束\");\n }\n }\n //上面并没有返回System.out.println(annotation.jdbcUrl())结果,\n 要设置生命周期,在注解类上加@Retention(RetentionPolicy.RUNTIME)\n \n 输出: \n jdbc:mysql://localhost:3306/test2\n root\n 运行结束\n \n //表示获取到TestAnnotaion类上的注解@MyAnnotation的信息\n //可以认定@MyAnnotation(driverClass = \"\", jdbcUrl = \"jdbc:mysql://localhost:3306/test2\")是MyAnnotation类的实例对象\n //这里是通过反射获得MyAnnotation对象\n //其实@MyAnnotation()相当于new了一个对象\n //如果数组属性中只有一个元素,这时候属性值部分可以省略大括号。\n \n 可以加上@Target({ElementType.METHOD,ElementType.TYPE})\n 表示只能在方法和类上加此注解\n \n 枚举类型注解属性\n 在注解类中添加 MyEnum season() default MyEnum.Winter;\n 则被注解类可添加 @MyAnnotation(season = MyEnum.Autumn)","slug":"201703092044_annotation","published":1,"date":"2020-01-15T05:50:39.599Z","updated":"2018-01-13T02:29:22.145Z","layout":"post","photos":[],"link":"","_id":"ckm3invdq007224ujwp2dxbq0","content":"

知识来源:

\n
\n

http://blog.csdn.net/vebasan/article/details/4794699
http://www.cnblogs.com/liuyonglong/p/3567786.html
http://www.oracle.com/technetwork/cn/topics/linux/hunter-meta-097643-zhs.html
http://developer.51cto.com/art/200909/152828.htm
http://blog.csdn.net/yerenyuan_pku/article/details/52583656
http://www.cnblogs.com/lzq2016/p/5169475.html

\n
\n\n

J2SE 5.0 版本以后新特性 Annotation(注解)

定义: 元数据(MetaData)是数据的数据。元数据是添加到程序元素如方法、字段、类和包上的额外信息。或者说是从信息资源中抽取出来的用于说明其特征、内容的结构化的数据

>例如:富士苹果有一个属性:它是红色的。假定有一个 FushiApple 类,可以使用 @Color 批注类型的一个批注来指定它的颜色。通过这么做,您就提供了关于苹果的元数据。        \n

作用: 创建文档,跟踪代码中的依赖性,执行编译时检查,代码分析

>例如: spring等框架中可以大量运用注解来替代配置文件进行依赖注入(取代了复杂的XML配置文件)\n>       所以说注解其实就是元数据,本质上也是接口,而且是继承了接口Annotation的接口\n>利用元数据来描述资源后,我们就可以用来做很多的事情。比如确定资源,为资源提供检索点,在不同系统之间进行数据交换。\n>比如:我们把所有的控制层都打注解@Controller,就表示此类为控制层,为springMVC提供定位,表明这是控制器,然后在springMVC配置文件中加入<context:component-scan base-package=""/>用来识别就可以\n

##元数据的实现
JDK5.0出来后,java语言中就有了四种类型(TYPE),即类(class)、枚举(enum)、接口(interface)和注解(@interface),它们是处在同一级别的。java就是通过注解来表示元数据的。
java.lang.annotation.Annotation 本身是接口,而不是注解,当使用关键字@interface 定义一个注解时,该注解隐含的继承了java.lang.annotation.Annotation接口;
如果我们定义一个接口,并且让该接口继承自Annotation,并不能作为注解, 定义注解只能依靠@interface实现

\n

JDK提供的基本注解

    \n
  1. @SuppressWarnings 压制警告

    \n
    参数: \n    1.deprecation :过时的类或方法警告。 \n    2.unchecked:执行了未检查的转换时警告。 \n    3.fallthrough:当Switch程序块直接通往下一种情况而没有Break时的警告。\n    4.path:在类路径、源文件路径等中有不存在的路径时的警告。\n    5.serial:当在可序列化的类上缺少serialVersionUID定义时的警告。\n    6.finally:任何finally子句不能完成时的警告。\n    7.all:关于以上所有情况的警告。\n
  2. \n
\n
    \n
  1. @Deprecated 设置过时
  2. \n
  3. @Override 表示复写
  4. \n
\n

元注解(注解的注解) 用于修饰一个Annotation的定义

    \n
  1. @Retention 设置注解的生命周期
  2. \n
\n
RetentionPolicy.SOURCE      java源文件     只在源代码级别保留,编译时就会被忽略\nRetentionPolicy.CLASS       class文件     编译器将把注解记录在class文件中,当运行Java程序时,JVM会忽略注解。这是默认值。\nRetentionPolicy.RUNTIME     内存的字节码  编译器将把注解记录在class文件中。当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解\n
    \n
  1. @Target 表示该注解可以用在什么地方
  2. \n
\n
参数:value \n类型:ElementType   ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,... 分别表示注解的不同的使用地方\n
    \n
  1. @Document 将注解包含在javadoc中 指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档

    \n
  2. \n
  3. @Inherited 被它修饰的Annotation将具有继承性 允许子类继承父类的注解 其子类将自动具有该注解

    \n
  4. \n
\n

注解和XML配置文件对比

XML配置文件与代码文件分离,不利于一致性维护,缺乏在运行时的反射机制。而Annotation与代码一起被编译器处理,并能够在运行时访问。 \n通常XML配置文件都很复杂而且冗长。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。 \nXML配置文件是文本文件,没有显式的类型支持,需要到运行时刻才能发现隐藏的错误。而Annotation是类型安全的,它会被编译器检查。   \nXML文件可以表达复杂的关系,但是在注释中我们却很难表达复杂的或层次的结构。 \nXML配置文件是在代码之外被单独处理的,也就是说基于XML的配置信息不是硬编码的,可以部署的时候进行修改。而修改Annotation则需要进行重新编译,\n不过我们可以利用AOP提供的机制为已有的代码添加Annotation。通过部署不同的AOP模块,就能使代码具有不同的Annotation,但比起直接修改XML显得复杂。 \n注释是简单易用的,并且对大多数应用来说已经足够了。而XML文件更复杂,但具有部署的灵活性,因而被用来处理与部署相关的决策。\n注释与XML配置文件可以一起使用。由于注释只能保存相当少的配置信息,只有预先集成的框架组件(类似在框架组件中已经完成了大多数预备工作)可以广泛地把注释作为配置选项。\n而XML配置文件作为一个可选的重载机制,可以用于改变注释的默认行为。\n

创建注解

1.定义一个注解类

\n
//@Retention(RetentionPolicy.RUNTIME)\npublic @interface MyAnnotation {\n    //公共的final静态属性 默认加上public static final  必须初始化\n    String user = "root";\n    String password = "fangshuoit";\n\n    //公共的抽象方法  默认加上public abstract 调用时必须初始化\n    //可以有默认返回值(返回值类型:8种基本类型,String、Class、枚举、注解及这些类型的数组)\n    String driverClass() default "com.mysql.jdbc.Driver";\n    String jdbcUrl() default "jdbc:mysql://localhost:3306/test";\n    String password() default "fangshuoit";\n\n    int[] arrayAttr() default {3,4,4};\n}\n

2.在另一个类或方法上加上此注解,并且添加一个测试方法测试有没有此注解

\n
@MyAnnotation(jdbcUrl = "jdbc:mysql://localhost:3306/test2")\npublic class TestAnnotaion {\n    private String name;\n\n    @Test\n    @MyAnnotation(arrayAttr={2,3,4}, jdbcUrl = "jdbc:mysql://localhost:3306/test3")\n    public void test() {\n\n    }\n\n    @SuppressWarnings("deprecation")\n    public static void main(String[] args) {\n        System.runFinalizersOnExit(true);\n        if (TestAnnotaion.class.isAnnotationPresent(MyAnnotation.class)) { // 类上是否有注解,默认情况下返回false\n            MyAnnotation annotation = TestAnnotaion.class.getAnnotation(MyAnnotation.class);\n            System.out.println(annotation.jdbcUrl()); \n            System.out.println(annotation.user);\n        }\n        System.out.println("运行结束");\n    }\n}\n//上面并没有返回System.out.println(annotation.jdbcUrl())结果,\n要设置生命周期,在注解类上加@Retention(RetentionPolicy.RUNTIME)\n\n输出:   \n        jdbc:mysql://localhost:3306/test2\n        root\n        运行结束\n\n//表示获取到TestAnnotaion类上的注解@MyAnnotation的信息\n//可以认定@MyAnnotation(driverClass = "", jdbcUrl = "jdbc:mysql://localhost:3306/test2")是MyAnnotation类的实例对象\n//这里是通过反射获得MyAnnotation对象\n//其实@MyAnnotation()相当于new了一个对象\n//如果数组属性中只有一个元素,这时候属性值部分可以省略大括号。\n\n可以加上@Target({ElementType.METHOD,ElementType.TYPE})\n表示只能在方法和类上加此注解\n\n枚举类型注解属性\n    在注解类中添加 MyEnum season() default MyEnum.Winter;\n    则被注解类可添加 @MyAnnotation(season = MyEnum.Autumn)\n
","site":{"data":{}},"excerpt":"

知识来源:

\n
\n

http://blog.csdn.net/vebasan/article/details/4794699
http://www.cnblogs.com/liuyonglong/p/3567786.html
http://www.oracle.com/technetwork/cn/topics/linux/hunter-meta-097643-zhs.html
http://developer.51cto.com/art/200909/152828.htm
http://blog.csdn.net/yerenyuan_pku/article/details/52583656
http://www.cnblogs.com/lzq2016/p/5169475.html

\n
","more":"

J2SE 5.0 版本以后新特性 Annotation(注解)

定义: 元数据(MetaData)是数据的数据。元数据是添加到程序元素如方法、字段、类和包上的额外信息。或者说是从信息资源中抽取出来的用于说明其特征、内容的结构化的数据

>例如:富士苹果有一个属性:它是红色的。假定有一个 FushiApple 类,可以使用 @Color 批注类型的一个批注来指定它的颜色。通过这么做,您就提供了关于苹果的元数据。        \n

作用: 创建文档,跟踪代码中的依赖性,执行编译时检查,代码分析

>例如: spring等框架中可以大量运用注解来替代配置文件进行依赖注入(取代了复杂的XML配置文件)\n>       所以说注解其实就是元数据,本质上也是接口,而且是继承了接口Annotation的接口\n>利用元数据来描述资源后,我们就可以用来做很多的事情。比如确定资源,为资源提供检索点,在不同系统之间进行数据交换。\n>比如:我们把所有的控制层都打注解@Controller,就表示此类为控制层,为springMVC提供定位,表明这是控制器,然后在springMVC配置文件中加入<context:component-scan base-package=""/>用来识别就可以\n

##元数据的实现
JDK5.0出来后,java语言中就有了四种类型(TYPE),即类(class)、枚举(enum)、接口(interface)和注解(@interface),它们是处在同一级别的。java就是通过注解来表示元数据的。
java.lang.annotation.Annotation 本身是接口,而不是注解,当使用关键字@interface 定义一个注解时,该注解隐含的继承了java.lang.annotation.Annotation接口;
如果我们定义一个接口,并且让该接口继承自Annotation,并不能作为注解, 定义注解只能依靠@interface实现

\n

JDK提供的基本注解

    \n
  1. @SuppressWarnings 压制警告

    \n
    参数: \n    1.deprecation :过时的类或方法警告。 \n    2.unchecked:执行了未检查的转换时警告。 \n    3.fallthrough:当Switch程序块直接通往下一种情况而没有Break时的警告。\n    4.path:在类路径、源文件路径等中有不存在的路径时的警告。\n    5.serial:当在可序列化的类上缺少serialVersionUID定义时的警告。\n    6.finally:任何finally子句不能完成时的警告。\n    7.all:关于以上所有情况的警告。\n
  2. \n
\n
    \n
  1. @Deprecated 设置过时
  2. \n
  3. @Override 表示复写
  4. \n
\n

元注解(注解的注解) 用于修饰一个Annotation的定义

    \n
  1. @Retention 设置注解的生命周期
  2. \n
\n
RetentionPolicy.SOURCE      java源文件     只在源代码级别保留,编译时就会被忽略\nRetentionPolicy.CLASS       class文件     编译器将把注解记录在class文件中,当运行Java程序时,JVM会忽略注解。这是默认值。\nRetentionPolicy.RUNTIME     内存的字节码  编译器将把注解记录在class文件中。当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解\n
    \n
  1. @Target 表示该注解可以用在什么地方
  2. \n
\n
参数:value \n类型:ElementType   ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,... 分别表示注解的不同的使用地方\n
    \n
  1. @Document 将注解包含在javadoc中 指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档

    \n
  2. \n
  3. @Inherited 被它修饰的Annotation将具有继承性 允许子类继承父类的注解 其子类将自动具有该注解

    \n
  4. \n
\n

注解和XML配置文件对比

XML配置文件与代码文件分离,不利于一致性维护,缺乏在运行时的反射机制。而Annotation与代码一起被编译器处理,并能够在运行时访问。 \n通常XML配置文件都很复杂而且冗长。Java注释则不同,它是代码的一部分,不需要额外的引用就可以指明配置信息。 \nXML配置文件是文本文件,没有显式的类型支持,需要到运行时刻才能发现隐藏的错误。而Annotation是类型安全的,它会被编译器检查。   \nXML文件可以表达复杂的关系,但是在注释中我们却很难表达复杂的或层次的结构。 \nXML配置文件是在代码之外被单独处理的,也就是说基于XML的配置信息不是硬编码的,可以部署的时候进行修改。而修改Annotation则需要进行重新编译,\n不过我们可以利用AOP提供的机制为已有的代码添加Annotation。通过部署不同的AOP模块,就能使代码具有不同的Annotation,但比起直接修改XML显得复杂。 \n注释是简单易用的,并且对大多数应用来说已经足够了。而XML文件更复杂,但具有部署的灵活性,因而被用来处理与部署相关的决策。\n注释与XML配置文件可以一起使用。由于注释只能保存相当少的配置信息,只有预先集成的框架组件(类似在框架组件中已经完成了大多数预备工作)可以广泛地把注释作为配置选项。\n而XML配置文件作为一个可选的重载机制,可以用于改变注释的默认行为。\n

创建注解

1.定义一个注解类

\n
//@Retention(RetentionPolicy.RUNTIME)\npublic @interface MyAnnotation {\n    //公共的final静态属性 默认加上public static final  必须初始化\n    String user = "root";\n    String password = "fangshuoit";\n\n    //公共的抽象方法  默认加上public abstract 调用时必须初始化\n    //可以有默认返回值(返回值类型:8种基本类型,String、Class、枚举、注解及这些类型的数组)\n    String driverClass() default "com.mysql.jdbc.Driver";\n    String jdbcUrl() default "jdbc:mysql://localhost:3306/test";\n    String password() default "fangshuoit";\n\n    int[] arrayAttr() default {3,4,4};\n}\n

2.在另一个类或方法上加上此注解,并且添加一个测试方法测试有没有此注解

\n
@MyAnnotation(jdbcUrl = "jdbc:mysql://localhost:3306/test2")\npublic class TestAnnotaion {\n    private String name;\n\n    @Test\n    @MyAnnotation(arrayAttr={2,3,4}, jdbcUrl = "jdbc:mysql://localhost:3306/test3")\n    public void test() {\n\n    }\n\n    @SuppressWarnings("deprecation")\n    public static void main(String[] args) {\n        System.runFinalizersOnExit(true);\n        if (TestAnnotaion.class.isAnnotationPresent(MyAnnotation.class)) { // 类上是否有注解,默认情况下返回false\n            MyAnnotation annotation = TestAnnotaion.class.getAnnotation(MyAnnotation.class);\n            System.out.println(annotation.jdbcUrl()); \n            System.out.println(annotation.user);\n        }\n        System.out.println("运行结束");\n    }\n}\n//上面并没有返回System.out.println(annotation.jdbcUrl())结果,\n要设置生命周期,在注解类上加@Retention(RetentionPolicy.RUNTIME)\n\n输出:   \n        jdbc:mysql://localhost:3306/test2\n        root\n        运行结束\n\n//表示获取到TestAnnotaion类上的注解@MyAnnotation的信息\n//可以认定@MyAnnotation(driverClass = "", jdbcUrl = "jdbc:mysql://localhost:3306/test2")是MyAnnotation类的实例对象\n//这里是通过反射获得MyAnnotation对象\n//其实@MyAnnotation()相当于new了一个对象\n//如果数组属性中只有一个元素,这时候属性值部分可以省略大括号。\n\n可以加上@Target({ElementType.METHOD,ElementType.TYPE})\n表示只能在方法和类上加此注解\n\n枚举类型注解属性\n    在注解类中添加 MyEnum season() default MyEnum.Winter;\n    则被注解类可添加 @MyAnnotation(season = MyEnum.Autumn)\n
"},{"title":"数据结构随笔","comments":1,"description":null,"_content":"\n# *线性表*\n\n| | 顺序存储 | 链式存储 |\n| ------------- |:-------------:| -----:|\n| 分配方式 | 连续的存储单元 依次存储 | 链式存储结构 |\n| 查找时间复杂度 | O(1) | O(n) |\n| 更新时间复杂度 | O(n) | O(1) |\n| 空间 | 固定 | 可扩展 |\n\n## 顺序存储\n\n```text\n\n 封装顺序存储结构 三个条件\n 1.起始位置\n 2.最大容量\n 3.当前长度\n \n 优点: 1.无需为表中逻辑关系增加额外的存储空间(空间是紧挨着的) 2.读取元素速度快\n 缺点: 1.插入,删除操作复杂速度慢 2.易造成空间碎片化 \n \n EG: ***********************************\n #define MAXSIZE 20\n typedef int ElemType;\n typedef struct\n {\n ElemType data[MAXSIZE];//内存中的存储位置页数连续的 下标从0开始\n int length; //当前长度\n } SqList;\n \n ***********************************\n \n``` \n\n\n\n> 顺序存储元素操作\n\n\n```cpp\n\n// 1. 获取list中的某个元素\n\n#define OK 1\n#define ERROR 0\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Status; //返回值类型 状态码\n\n// i是取第几个位置的值 则其位置下标为 i-1; *e即为获取的元素\nStatus GetElem(SqList L, int i, ElemType *e)\n{\n if(L.length==0 || i>L.length)\n {\n return ERROR;\n }\n\n *e = L.data[i-1];\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg c_1.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 2. 插入元素到list的指定的位置\n\nStatus ListInsert(SqList *L, int i, ElemType e) //i插入的位置 e插入的数据\n{\n int k;\n\n if(L -> length == MAXSIZE)//判断List顺序表的实际长度是否已经达到最大长度,即是否已经满了 ( -> 用来取子数据)\n {\n return ERROR;\n }\n\n if(i<1 || i>L->length+1) //判断i在不在已有数据组成的表范围之内\n {\n return ERROR;\n }\n\n if(i<=L->length) //在合理的范围内\n {\n for(k=L->length-1; k>= i-1; k--)//从后往前一个一个向后移动一位 (eg: a[1]是第二个元素)\n {\n L->data[k+1] = L->data[k];\n }\n }\n\n L->data[i-1] = e; //把数据插入到该位置\n L->length++; //表长度增加\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg c_b.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 3. 删除元素从list的指定的位置\n\nStatus ListDelete(SqList *L, int i, ElemType *e)// i是删除的位置 *e是删除的元素\n{\n int k;\n\n if(L->length == 0)\n {\n return ERROR;\n }\n\n if(i<1 || i > L->length)\n {\n return ERROR;\n }\n\n *e = L->data[i-1]; //被删除的元素\n\n if(i >= 1) //在合理的范围内\n {\n for(k=i; k<= L->length-1; k++)//从前往后一个一个向前移动一位 (eg: a[1]是第二个元素)\n {\n L->data[k-1] = L->data[k];\n }\n }\n\n L->length--; //表长度减少\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg c_c.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n---\n\n \n## 链式存储\n\n\n 特色: 用一组任意的存储单元存储线性表的数据元素, 每个地址叫结点, 需要存储元素(数据域)和其后继元素的地址(指针域, 内部数据叫指针或链)\n \n *只有一个指针域 --> 单链表\n \n *头指针 -- > 指向头结点的指针(永不为空,即使链表为空); \n *头结点 --> 第一个结点 (不存储数据,数据域可以存放链表长度) ; \n *最后一个结点指向空(NULL); \n *空链表 -- > 有头结点和头指针, 没有其他结点, 直接指向NULL\n \n \n EG: ***********************************\n typedef struct Node\n {\n ElemType data; //数据域\n struct Node* next; //指针域\n } Node;\n \n typedef struct Node* LinkList; //取Node*的别名为LinkList\n \n //LinkList p; p->data p->next\n ***********************************\n\n\n> 链式存储元素操作\n\n```cpp \n// 1. 获取list中的某个元素\n\nStatus GetElemL(LinkList L, int i, ElemType *e)\n{\n int j;\n LinkList p; //p即为一个指针 指向链表\n \n p = L->next;//使p指向当前链表的第一个结点\n j = 1;\n \n while( p && j < i)//判断p不为空 且没到要查询的结点 查到或者到头了就退出循环\n {\n p = p->next;\n ++j;\n }\n \n if(!p || j > i) //没找到 \n {\n return ERROR;\n }\n \n *e = p->data;\n \n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c13.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 2. 插入元素到list的指定的位置\n\nStatus ListInsertL(LinkList L, int i, ElemType e) //i插入的位置 e插入的数据\n{\n int j;\n LinkList p, s;\n\n p = L;\n j = 1;\n\n while(p && jnext;\n j++;\n }\n\n if(!p || j>i) // 判断要插入的位置存在\n {\n return ERROR;\n }\n\n s = (LinkList)malloc(sizeof(Node)); //生成新的结点 结点大小=sizeof(Node) 然后强转\n s->data = e; //设置结点数据域\n\n s->next = p->next; //把插入结点的上一个结点的指针赋给要插入的结点的指针域\n p->next = s; //把插入结点的上一个结点的指针指向插入的结点\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c14.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 3. 删除元素从list的指定的位置\n\nStatus ListRemove(LinkList L, int i, ElemType *e)// i是删除的位置 *e是删除的元素\n{\n int j;\n LinkList p, q;\n\n p = L; //先指向头结点, 要删除的结点从头结点指向的结点开始算起;即到最后要删除结点是p->next\n j = 1;\n\n while(p->next && jnext;\n j++;\n }\n\n if(!(p->next) || j>i) // 判断要删除的结点存在\n {\n return ERROR;\n }\n\n q = p->next; //q结点是要删除的结点\n p->next = q->next; //q结点指针域可能指向null(q是最后一个结点时候)\n\n free(q); //释放空间\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c15.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n\n> 链式存储整表创建(建立单链表) (顺序存储 == 数组)\n\n#### 头插法\n\n```cpp\n\n// 1. 头插法建立单链表: \n// 把新加入的结点插入到链表头部, 把头结点指向新插入的结点(会使插入的结点顺序与原先设定的顺序颠倒)\n\nvoid CreateListHead(LinkList *L, int n)\n{\n LinkList p;\n int i;\n\n// srand(time(0)); //初始化随机数\n\n //这里L是指针 *L表示链表头结点\n *L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点(可以放在函数外面)\n (*L)->next = NULL; //设置头结点默认指向null\n\n for(i=0; idata = rand() % 100 + 1; //随机数赋值到每个结点的数据域\n p->next = (*L)->next; //把原来的头结点指针指向的地址赋值给新建的结点(因为要插入到头部)\n (*L)->next = p; //更新头结点指针域\n }\n}\n\n```\n> 测试结果:\n\n{% qnimg bolg_c10.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n\n#### 尾插法\n\n```cpp\n// 1. 尾插法建立单链表: \n\nvoid CreateListTail(LinkList *L, int n)\n{\n LinkList p, r;\n int i;\n\n// srand(time(0));\n\n *L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点\n r = *L; //使r指向生成的链表(当前只有头结点 所以r指向头结点) /*定义一个临时指针变量指向表头*/\n\n for(i=0; idata = rand() % 100 + 1; //随机数赋值到每个结点的数据域\n r->next = p; //使r的指针域指向新的结点\n r = p; //把新结点赋给r r指向链表的最后一个结点\n }\n r->next = NULL; //当全部插入的之后 要把最后一个节点的指针域指向NULL\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c12.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n\n\n> 链式存储整表删除\n\n\n```cpp\nStatus ClearList(LinkList L)\n{\n LinkList p, q;\n \n p = L->next;\n \n while(p)\n {\n q = p->next;\n free(p); //p指向每次都释放的地址\n p = q;\n }\n \n L->next = NULL;\n \n return OK;\n}\n\n \n## 静态链表: 用数组实现链表\n\n#### 定义\n \n #define MAXSIZE 1000\n typedef struct\n {\n ElemType data; //数据\n int cur; //游标(Cursor)\n } Component, StaticLinkList[MAXSIZE];\n \n\n\n#### 初始化\n\n//初始化\nvoid InitList(StaticLinkList space)\n{\n int i;\n for(i=0; i < MAXSIZE-1; i++)\n {\n space[i].cur = i + 1; \n }\n \n space[MAXSIZE-1].cur = 1; //最后一个结点存放有数据的第一个节点下标\n}\n\n//返回链表长度\nint ListLength(StaticLinkList L) {\n int i, j = 0;\n i = L[0].cur; //i指向第一个结点\n\n while (i) //最后一个结点的指针域为0,结束循环\n {\n ++j;\n i = L[i].cur;\n }\n return j;\n}\n \n```\n \n#### 插入新元素到静态链表\n\n{% qnimg 20150819110834176.jpg title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n> 需要先获取一个空闲的链表位置, 插入数据,再把该位置插入到指定位置(其实就是更改cur, 使其链接起来)\n\n```cpp \n//申请分配一个空闲节点\nint malloc_sl(StaticLinkList space) {//分配空闲节点\n int i = space[0].cur;//总是取头结点之后的第一个空闲结点做分配,同时空闲链表非空,头结点做调整\n if (space[0].cur) {\n space[0].cur = space[i].cur;//空闲链表头结点调整指针域(把分配结点的cur存放在头结点)\n }\n return i;//返回申请到的空闲节点的数组下标\n}\n\n//插入数据到指定节点之前 i的前一个节点\nvoid ListInsert(StaticLinkList L, int i, ElemType e) {//e是新插入的元素 i是要插入的位置(插入到第i个元素之前)\n int j, k, l;\n\n k = MAXSIZE - 1; //数组最后一个元素\n\n if (i < 1 || i > ListLength(L)) {\n return;\n }\n\n j = malloc_sl(L); //申请一个节点, j是下标\n if (j) { // C里面非零就是真\n L[j].data = e;//赋值到静态链表指定位置\n for (l = 1; l <= i - 1; l++) {\n k = L[k].cur; //目的是 通过循环获取到第(i-1)个元素的下标,其下标为k,其cur为i\n }\n\n L[j].cur = L[k].cur; //把下标为(i-1)的元素的cur赋值给第下标为j的元素的cur: 即把插入节点的\"指针\"指向要插入的节点之前(i)\n L[k].cur = j; //把下标为(i-1)的元素的cur设置为新插入的节点; 原理和动态链表很相似\n }\n}\n```\n \n\n#### 静态链表删除元素\n\n{% qnimg 20150819173342013.jpg title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n> 要先修改游标(把该节点的游标赋值到其上一个节点的游标) 然后在删除节点数据, 再把该节点接到后面的空闲节点上\n\n```cpp\nvoid free_sll(StaticLinkList L, int i) {\n L[i].cur = L[0].cur; // 头结点中存放的是第一个空闲节点下标 把该下标赋值给删除的节点\n L[0].cur = i; //把要删除的节点的下标赋值给头结点;\n L[i].data = 0; \n}\n\n// 删除指定节点数据(下标减1的节点) L[i-1]\nvoid ListDelete(StaticLinkList L, int i) {\n int j, k; \n\n k = MAXSIZE - 1;\n if (i < 1 || i > ListLength(L)) {\n return;\n }\n\n for (j = 1; j <= i - 1; j++) {\n k = L[k].cur; //循环得到要删除的结点的前一个节点(不是物理的前一个,而是cur是i-1的节点)\n }\n\n j = L[k].cur; //获取要删除的结点的cur赋值到j\n L[k].cur = L[j].cur;//把j赋值给该节点\n\n free_sll(L, j);//\"释放\"节点数据\n}\n\n```\n\n## 循环链表\n\n#### 定义: 最后一个结点的指针域指向头结点,整个链表形成一个环\n\n> 判断空链表的条件是 head == head->next;","source":"_posts/20170505_line.md","raw":"---\ntitle: 数据结构随笔\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n# *线性表*\n\n| | 顺序存储 | 链式存储 |\n| ------------- |:-------------:| -----:|\n| 分配方式 | 连续的存储单元 依次存储 | 链式存储结构 |\n| 查找时间复杂度 | O(1) | O(n) |\n| 更新时间复杂度 | O(n) | O(1) |\n| 空间 | 固定 | 可扩展 |\n\n## 顺序存储\n\n```text\n\n 封装顺序存储结构 三个条件\n 1.起始位置\n 2.最大容量\n 3.当前长度\n \n 优点: 1.无需为表中逻辑关系增加额外的存储空间(空间是紧挨着的) 2.读取元素速度快\n 缺点: 1.插入,删除操作复杂速度慢 2.易造成空间碎片化 \n \n EG: ***********************************\n #define MAXSIZE 20\n typedef int ElemType;\n typedef struct\n {\n ElemType data[MAXSIZE];//内存中的存储位置页数连续的 下标从0开始\n int length; //当前长度\n } SqList;\n \n ***********************************\n \n``` \n\n\n\n> 顺序存储元素操作\n\n\n```cpp\n\n// 1. 获取list中的某个元素\n\n#define OK 1\n#define ERROR 0\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Status; //返回值类型 状态码\n\n// i是取第几个位置的值 则其位置下标为 i-1; *e即为获取的元素\nStatus GetElem(SqList L, int i, ElemType *e)\n{\n if(L.length==0 || i>L.length)\n {\n return ERROR;\n }\n\n *e = L.data[i-1];\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg c_1.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 2. 插入元素到list的指定的位置\n\nStatus ListInsert(SqList *L, int i, ElemType e) //i插入的位置 e插入的数据\n{\n int k;\n\n if(L -> length == MAXSIZE)//判断List顺序表的实际长度是否已经达到最大长度,即是否已经满了 ( -> 用来取子数据)\n {\n return ERROR;\n }\n\n if(i<1 || i>L->length+1) //判断i在不在已有数据组成的表范围之内\n {\n return ERROR;\n }\n\n if(i<=L->length) //在合理的范围内\n {\n for(k=L->length-1; k>= i-1; k--)//从后往前一个一个向后移动一位 (eg: a[1]是第二个元素)\n {\n L->data[k+1] = L->data[k];\n }\n }\n\n L->data[i-1] = e; //把数据插入到该位置\n L->length++; //表长度增加\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg c_b.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 3. 删除元素从list的指定的位置\n\nStatus ListDelete(SqList *L, int i, ElemType *e)// i是删除的位置 *e是删除的元素\n{\n int k;\n\n if(L->length == 0)\n {\n return ERROR;\n }\n\n if(i<1 || i > L->length)\n {\n return ERROR;\n }\n\n *e = L->data[i-1]; //被删除的元素\n\n if(i >= 1) //在合理的范围内\n {\n for(k=i; k<= L->length-1; k++)//从前往后一个一个向前移动一位 (eg: a[1]是第二个元素)\n {\n L->data[k-1] = L->data[k];\n }\n }\n\n L->length--; //表长度减少\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg c_c.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n---\n\n \n## 链式存储\n\n\n 特色: 用一组任意的存储单元存储线性表的数据元素, 每个地址叫结点, 需要存储元素(数据域)和其后继元素的地址(指针域, 内部数据叫指针或链)\n \n *只有一个指针域 --> 单链表\n \n *头指针 -- > 指向头结点的指针(永不为空,即使链表为空); \n *头结点 --> 第一个结点 (不存储数据,数据域可以存放链表长度) ; \n *最后一个结点指向空(NULL); \n *空链表 -- > 有头结点和头指针, 没有其他结点, 直接指向NULL\n \n \n EG: ***********************************\n typedef struct Node\n {\n ElemType data; //数据域\n struct Node* next; //指针域\n } Node;\n \n typedef struct Node* LinkList; //取Node*的别名为LinkList\n \n //LinkList p; p->data p->next\n ***********************************\n\n\n> 链式存储元素操作\n\n```cpp \n// 1. 获取list中的某个元素\n\nStatus GetElemL(LinkList L, int i, ElemType *e)\n{\n int j;\n LinkList p; //p即为一个指针 指向链表\n \n p = L->next;//使p指向当前链表的第一个结点\n j = 1;\n \n while( p && j < i)//判断p不为空 且没到要查询的结点 查到或者到头了就退出循环\n {\n p = p->next;\n ++j;\n }\n \n if(!p || j > i) //没找到 \n {\n return ERROR;\n }\n \n *e = p->data;\n \n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c13.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 2. 插入元素到list的指定的位置\n\nStatus ListInsertL(LinkList L, int i, ElemType e) //i插入的位置 e插入的数据\n{\n int j;\n LinkList p, s;\n\n p = L;\n j = 1;\n\n while(p && jnext;\n j++;\n }\n\n if(!p || j>i) // 判断要插入的位置存在\n {\n return ERROR;\n }\n\n s = (LinkList)malloc(sizeof(Node)); //生成新的结点 结点大小=sizeof(Node) 然后强转\n s->data = e; //设置结点数据域\n\n s->next = p->next; //把插入结点的上一个结点的指针赋给要插入的结点的指针域\n p->next = s; //把插入结点的上一个结点的指针指向插入的结点\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c14.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n__________________________________________________________\n\n```cpp\n\n// 3. 删除元素从list的指定的位置\n\nStatus ListRemove(LinkList L, int i, ElemType *e)// i是删除的位置 *e是删除的元素\n{\n int j;\n LinkList p, q;\n\n p = L; //先指向头结点, 要删除的结点从头结点指向的结点开始算起;即到最后要删除结点是p->next\n j = 1;\n\n while(p->next && jnext;\n j++;\n }\n\n if(!(p->next) || j>i) // 判断要删除的结点存在\n {\n return ERROR;\n }\n\n q = p->next; //q结点是要删除的结点\n p->next = q->next; //q结点指针域可能指向null(q是最后一个结点时候)\n\n free(q); //释放空间\n\n return OK;\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c15.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n\n> 链式存储整表创建(建立单链表) (顺序存储 == 数组)\n\n#### 头插法\n\n```cpp\n\n// 1. 头插法建立单链表: \n// 把新加入的结点插入到链表头部, 把头结点指向新插入的结点(会使插入的结点顺序与原先设定的顺序颠倒)\n\nvoid CreateListHead(LinkList *L, int n)\n{\n LinkList p;\n int i;\n\n// srand(time(0)); //初始化随机数\n\n //这里L是指针 *L表示链表头结点\n *L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点(可以放在函数外面)\n (*L)->next = NULL; //设置头结点默认指向null\n\n for(i=0; idata = rand() % 100 + 1; //随机数赋值到每个结点的数据域\n p->next = (*L)->next; //把原来的头结点指针指向的地址赋值给新建的结点(因为要插入到头部)\n (*L)->next = p; //更新头结点指针域\n }\n}\n\n```\n> 测试结果:\n\n{% qnimg bolg_c10.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n\n#### 尾插法\n\n```cpp\n// 1. 尾插法建立单链表: \n\nvoid CreateListTail(LinkList *L, int n)\n{\n LinkList p, r;\n int i;\n\n// srand(time(0));\n\n *L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点\n r = *L; //使r指向生成的链表(当前只有头结点 所以r指向头结点) /*定义一个临时指针变量指向表头*/\n\n for(i=0; idata = rand() % 100 + 1; //随机数赋值到每个结点的数据域\n r->next = p; //使r的指针域指向新的结点\n r = p; //把新结点赋给r r指向链表的最后一个结点\n }\n r->next = NULL; //当全部插入的之后 要把最后一个节点的指针域指向NULL\n}\n\n```\n> 测试结果:\n\n{% qnimg blog_c12.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n__________________________________________________________\n\n\n\n> 链式存储整表删除\n\n\n```cpp\nStatus ClearList(LinkList L)\n{\n LinkList p, q;\n \n p = L->next;\n \n while(p)\n {\n q = p->next;\n free(p); //p指向每次都释放的地址\n p = q;\n }\n \n L->next = NULL;\n \n return OK;\n}\n\n \n## 静态链表: 用数组实现链表\n\n#### 定义\n \n #define MAXSIZE 1000\n typedef struct\n {\n ElemType data; //数据\n int cur; //游标(Cursor)\n } Component, StaticLinkList[MAXSIZE];\n \n\n\n#### 初始化\n\n//初始化\nvoid InitList(StaticLinkList space)\n{\n int i;\n for(i=0; i < MAXSIZE-1; i++)\n {\n space[i].cur = i + 1; \n }\n \n space[MAXSIZE-1].cur = 1; //最后一个结点存放有数据的第一个节点下标\n}\n\n//返回链表长度\nint ListLength(StaticLinkList L) {\n int i, j = 0;\n i = L[0].cur; //i指向第一个结点\n\n while (i) //最后一个结点的指针域为0,结束循环\n {\n ++j;\n i = L[i].cur;\n }\n return j;\n}\n \n```\n \n#### 插入新元素到静态链表\n\n{% qnimg 20150819110834176.jpg title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n> 需要先获取一个空闲的链表位置, 插入数据,再把该位置插入到指定位置(其实就是更改cur, 使其链接起来)\n\n```cpp \n//申请分配一个空闲节点\nint malloc_sl(StaticLinkList space) {//分配空闲节点\n int i = space[0].cur;//总是取头结点之后的第一个空闲结点做分配,同时空闲链表非空,头结点做调整\n if (space[0].cur) {\n space[0].cur = space[i].cur;//空闲链表头结点调整指针域(把分配结点的cur存放在头结点)\n }\n return i;//返回申请到的空闲节点的数组下标\n}\n\n//插入数据到指定节点之前 i的前一个节点\nvoid ListInsert(StaticLinkList L, int i, ElemType e) {//e是新插入的元素 i是要插入的位置(插入到第i个元素之前)\n int j, k, l;\n\n k = MAXSIZE - 1; //数组最后一个元素\n\n if (i < 1 || i > ListLength(L)) {\n return;\n }\n\n j = malloc_sl(L); //申请一个节点, j是下标\n if (j) { // C里面非零就是真\n L[j].data = e;//赋值到静态链表指定位置\n for (l = 1; l <= i - 1; l++) {\n k = L[k].cur; //目的是 通过循环获取到第(i-1)个元素的下标,其下标为k,其cur为i\n }\n\n L[j].cur = L[k].cur; //把下标为(i-1)的元素的cur赋值给第下标为j的元素的cur: 即把插入节点的\"指针\"指向要插入的节点之前(i)\n L[k].cur = j; //把下标为(i-1)的元素的cur设置为新插入的节点; 原理和动态链表很相似\n }\n}\n```\n \n\n#### 静态链表删除元素\n\n{% qnimg 20150819173342013.jpg title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n> 要先修改游标(把该节点的游标赋值到其上一个节点的游标) 然后在删除节点数据, 再把该节点接到后面的空闲节点上\n\n```cpp\nvoid free_sll(StaticLinkList L, int i) {\n L[i].cur = L[0].cur; // 头结点中存放的是第一个空闲节点下标 把该下标赋值给删除的节点\n L[0].cur = i; //把要删除的节点的下标赋值给头结点;\n L[i].data = 0; \n}\n\n// 删除指定节点数据(下标减1的节点) L[i-1]\nvoid ListDelete(StaticLinkList L, int i) {\n int j, k; \n\n k = MAXSIZE - 1;\n if (i < 1 || i > ListLength(L)) {\n return;\n }\n\n for (j = 1; j <= i - 1; j++) {\n k = L[k].cur; //循环得到要删除的结点的前一个节点(不是物理的前一个,而是cur是i-1的节点)\n }\n\n j = L[k].cur; //获取要删除的结点的cur赋值到j\n L[k].cur = L[j].cur;//把j赋值给该节点\n\n free_sll(L, j);//\"释放\"节点数据\n}\n\n```\n\n## 循环链表\n\n#### 定义: 最后一个结点的指针域指向头结点,整个链表形成一个环\n\n> 判断空链表的条件是 head == head->next;","slug":"20170505_line","published":1,"date":"2020-01-15T05:50:39.609Z","updated":"2018-01-13T02:29:22.159Z","layout":"post","photos":[],"link":"","_id":"ckm3invdr007324uj1lcl2khd","content":"

线性表

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
顺序存储链式存储
分配方式连续的存储单元 依次存储链式存储结构
查找时间复杂度O(1)O(n)
更新时间复杂度O(n)O(1)
空间固定可扩展
\n

顺序存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

封装顺序存储结构 三个条件
1.起始位置
2.最大容量
3.当前长度

优点: 1.无需为表中逻辑关系增加额外的存储空间(空间是紧挨着的) 2.读取元素速度快
缺点: 1.插入,删除操作复杂速度慢 2.易造成空间碎片化

EG: ***********************************
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];//内存中的存储位置页数连续的 下标从0开始
int length; //当前长度
} SqList;

***********************************

```

<!--more-->

> 顺序存储元素操作


```cpp

// 1. 获取list中的某个元素

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status; //返回值类型 状态码

// i是取第几个位置的值 则其位置下标为 i-1; *e即为获取的元素
Status GetElem(SqList L, int i, ElemType *e)
{
if(L.length==0 || i>L.length)
{
return ERROR;
}

*e = L.data[i-1];

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 2. 插入元素到list的指定的位置

Status ListInsert(SqList *L, int i, ElemType e) //i插入的位置 e插入的数据
{
int k;

if(L -> length == MAXSIZE)//判断List顺序表的实际长度是否已经达到最大长度,即是否已经满了 ( -> 用来取子数据)
{
return ERROR;
}

if(i<1 || i>L->length+1) //判断i在不在已有数据组成的表范围之内
{
return ERROR;
}

if(i<=L->length) //在合理的范围内
{
for(k=L->length-1; k>= i-1; k--)//从后往前一个一个向后移动一位 (eg: a[1]是第二个元素)
{
L->data[k+1] = L->data[k];
}
}

L->data[i-1] = e; //把数据插入到该位置
L->length++; //表长度增加

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// 3. 删除元素从list的指定的位置

Status ListDelete(SqList *L, int i, ElemType *e)// i是删除的位置 *e是删除的元素
{
int k;

if(L->length == 0)
{
return ERROR;
}

if(i<1 || i > L->length)
{
return ERROR;
}

*e = L->data[i-1]; //被删除的元素

if(i >= 1) //在合理的范围内
{
for(k=i; k<= L->length-1; k++)//从前往后一个一个向前移动一位 (eg: a[1]是第二个元素)
{
L->data[k-1] = L->data[k];
}
}

L->length--; //表长度减少

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n

链式存储

特色: 用一组任意的存储单元存储线性表的数据元素, 每个地址叫结点, 需要存储元素(数据域)和其后继元素的地址(指针域, 内部数据叫指针或链)\n\n*只有一个指针域 --> 单链表\n\n*头指针 -- > 指向头结点的指针(永不为空,即使链表为空); \n*头结点 --> 第一个结点 (不存储数据,数据域可以存放链表长度) ; \n*最后一个结点指向空(NULL); \n*空链表 -- > 有头结点和头指针, 没有其他结点, 直接指向NULL\n\n\nEG: ***********************************\n            typedef struct Node\n            {\n                ElemType data;      //数据域\n                struct Node* next;  //指针域\n            } Node;\n\n            typedef struct Node* LinkList;  //取Node*的别名为LinkList\n\n            //LinkList p;   p->data  p->next\n    ***********************************\n
\n

链式存储元素操作

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 获取list中的某个元素

Status GetElemL(LinkList L, int i, ElemType *e)
{
int j;
LinkList p; //p即为一个指针 指向链表

p = L->next;//使p指向当前链表的第一个结点
j = 1;

while( p && j < i)//判断p不为空 且没到要查询的结点 查到或者到头了就退出循环
{
p = p->next;
++j;
}

if(!p || j > i) //没找到
{
return ERROR;
}

*e = p->data;

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 2. 插入元素到list的指定的位置

Status ListInsertL(LinkList L, int i, ElemType e) //i插入的位置 e插入的数据
{
int j;
LinkList p, s;

p = L;
j = 1;

while(p && j<i)//循环遍历链表 使p指向一个一个结点直到最后(p结点为null)或者到达要插入的地方
{
p = p->next;
j++;
}

if(!p || j>i) // 判断要插入的位置存在
{
return ERROR;
}

s = (LinkList)malloc(sizeof(Node)); //生成新的结点 结点大小=sizeof(Node) 然后强转
s->data = e; //设置结点数据域

s->next = p->next; //把插入结点的上一个结点的指针赋给要插入的结点的指针域
p->next = s; //把插入结点的上一个结点的指针指向插入的结点

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// 3. 删除元素从list的指定的位置

Status ListRemove(LinkList L, int i, ElemType *e)// i是删除的位置 *e是删除的元素
{
int j;
LinkList p, q;

p = L; //先指向头结点, 要删除的结点从头结点指向的结点开始算起;即到最后要删除结点是p->next
j = 1;

while(p->next && j<i)//循环遍历链表 使p指向一个一个结点直到最后(p结点指针指向null)或者到达要插入的地方
{
p = p->next;
j++;
}

if(!(p->next) || j>i) // 判断要删除的结点存在
{
return ERROR;
}

q = p->next; //q结点是要删除的结点
p->next = q->next; //q结点指针域可能指向null(q是最后一个结点时候)

free(q); //释放空间

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n

链式存储整表创建(建立单链表) (顺序存储 == 数组)

\n
\n

头插法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// 1. 头插法建立单链表:
// 把新加入的结点插入到链表头部, 把头结点指向新插入的结点(会使插入的结点顺序与原先设定的顺序颠倒)

void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;

// srand(time(0)); //初始化随机数

//这里L是指针 *L表示链表头结点
*L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点(可以放在函数外面)
(*L)->next = NULL; //设置头结点默认指向null

for(i=0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node)); //新建结点
p->data = rand() % 100 + 1; //随机数赋值到每个结点的数据域
p->next = (*L)->next; //把原来的头结点指针指向的地址赋值给新建的结点(因为要插入到头部)
(*L)->next = p; //更新头结点指针域
}
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n

尾插法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 尾插法建立单链表: 

void CreateListTail(LinkList *L, int n)
{
LinkList p, r;
int i;

// srand(time(0));

*L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点
r = *L; //使r指向生成的链表(当前只有头结点 所以r指向头结点) /*定义一个临时指针变量指向表头*/

for(i=0; i<n; i++)
{
p = (Node *)malloc(sizeof(Node)); //新建结点
p->data = rand() % 100 + 1; //随机数赋值到每个结点的数据域
r->next = p; //使r的指针域指向新的结点
r = p; //把新结点赋给r r指向链表的最后一个结点
}
r->next = NULL; //当全部插入的之后 要把最后一个节点的指针域指向NULL
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n

链式存储整表删除

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Status ClearList(LinkList L)
{
LinkList p, q;

p = L->next;

while(p)
{
q = p->next;
free(p); //p指向每次都释放的地址
p = q;
}

L->next = NULL;

return OK;
}


## 静态链表: 用数组实现链表

#### 定义

#define MAXSIZE 1000
typedef struct
{
ElemType data; //数据
int cur; //游标(Cursor)
} Component, StaticLinkList[MAXSIZE];



#### 初始化

//初始化
void InitList(StaticLinkList space)
{
int i;
for(i=0; i < MAXSIZE-1; i++)
{
space[i].cur = i + 1;
}

space[MAXSIZE-1].cur = 1; //最后一个结点存放有数据的第一个节点下标
}

//返回链表长度
int ListLength(StaticLinkList L) {
int i, j = 0;
i = L[0].cur; //i指向第一个结点

while (i) //最后一个结点的指针域为0,结束循环
{
++j;
i = L[i].cur;
}
return j;
}
\n

插入新元素到静态链表

\"图片说明\"\n
\n

需要先获取一个空闲的链表位置, 插入数据,再把该位置插入到指定位置(其实就是更改cur, 使其链接起来)

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//申请分配一个空闲节点
int malloc_sl(StaticLinkList space) {//分配空闲节点
int i = space[0].cur;//总是取头结点之后的第一个空闲结点做分配,同时空闲链表非空,头结点做调整
if (space[0].cur) {
space[0].cur = space[i].cur;//空闲链表头结点调整指针域(把分配结点的cur存放在头结点)
}
return i;//返回申请到的空闲节点的数组下标
}

//插入数据到指定节点之前 i的前一个节点
void ListInsert(StaticLinkList L, int i, ElemType e) {//e是新插入的元素 i是要插入的位置(插入到第i个元素之前)
int j, k, l;

k = MAXSIZE - 1; //数组最后一个元素

if (i < 1 || i > ListLength(L)) {
return;
}

j = malloc_sl(L); //申请一个节点, j是下标
if (j) { // C里面非零就是真
L[j].data = e;//赋值到静态链表指定位置
for (l = 1; l <= i - 1; l++) {
k = L[k].cur; //目的是 通过循环获取到第(i-1)个元素的下标,其下标为k,其cur为i
}

L[j].cur = L[k].cur; //把下标为(i-1)的元素的cur赋值给第下标为j的元素的cur: 即把插入节点的\"指针\"指向要插入的节点之前(i)
L[k].cur = j; //把下标为(i-1)的元素的cur设置为新插入的节点; 原理和动态链表很相似
}
}
\n

静态链表删除元素

\"图片说明\"\n
\n

要先修改游标(把该节点的游标赋值到其上一个节点的游标) 然后在删除节点数据, 再把该节点接到后面的空闲节点上

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void free_sll(StaticLinkList L, int i) {
L[i].cur = L[0].cur; // 头结点中存放的是第一个空闲节点下标 把该下标赋值给删除的节点
L[0].cur = i; //把要删除的节点的下标赋值给头结点;
L[i].data = 0;
}

// 删除指定节点数据(下标减1的节点) L[i-1]
void ListDelete(StaticLinkList L, int i) {
int j, k;

k = MAXSIZE - 1;
if (i < 1 || i > ListLength(L)) {
return;
}

for (j = 1; j <= i - 1; j++) {
k = L[k].cur; //循环得到要删除的结点的前一个节点(不是物理的前一个,而是cur是i-1的节点)
}

j = L[k].cur; //获取要删除的结点的cur赋值到j
L[k].cur = L[j].cur;//把j赋值给该节点

free_sll(L, j);//\"释放\"节点数据
}
\n

循环链表

定义: 最后一个结点的指针域指向头结点,整个链表形成一个环

\n

判断空链表的条件是 head == head->next;

\n
\n","site":{"data":{}},"excerpt":"","more":"

线性表

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
顺序存储链式存储
分配方式连续的存储单元 依次存储链式存储结构
查找时间复杂度O(1)O(n)
更新时间复杂度O(n)O(1)
空间固定可扩展
\n

顺序存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

封装顺序存储结构 三个条件
1.起始位置
2.最大容量
3.当前长度

优点: 1.无需为表中逻辑关系增加额外的存储空间(空间是紧挨着的) 2.读取元素速度快
缺点: 1.插入,删除操作复杂速度慢 2.易造成空间碎片化

EG: ***********************************
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];//内存中的存储位置页数连续的 下标从0开始
int length; //当前长度
} SqList;

***********************************

```

<!--more-->

> 顺序存储元素操作


```cpp

// 1. 获取list中的某个元素

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status; //返回值类型 状态码

// i是取第几个位置的值 则其位置下标为 i-1; *e即为获取的元素
Status GetElem(SqList L, int i, ElemType *e)
{
if(L.length==0 || i>L.length)
{
return ERROR;
}

*e = L.data[i-1];

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 2. 插入元素到list的指定的位置

Status ListInsert(SqList *L, int i, ElemType e) //i插入的位置 e插入的数据
{
int k;

if(L -> length == MAXSIZE)//判断List顺序表的实际长度是否已经达到最大长度,即是否已经满了 ( -> 用来取子数据)
{
return ERROR;
}

if(i<1 || i>L->length+1) //判断i在不在已有数据组成的表范围之内
{
return ERROR;
}

if(i<=L->length) //在合理的范围内
{
for(k=L->length-1; k>= i-1; k--)//从后往前一个一个向后移动一位 (eg: a[1]是第二个元素)
{
L->data[k+1] = L->data[k];
}
}

L->data[i-1] = e; //把数据插入到该位置
L->length++; //表长度增加

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// 3. 删除元素从list的指定的位置

Status ListDelete(SqList *L, int i, ElemType *e)// i是删除的位置 *e是删除的元素
{
int k;

if(L->length == 0)
{
return ERROR;
}

if(i<1 || i > L->length)
{
return ERROR;
}

*e = L->data[i-1]; //被删除的元素

if(i >= 1) //在合理的范围内
{
for(k=i; k<= L->length-1; k++)//从前往后一个一个向前移动一位 (eg: a[1]是第二个元素)
{
L->data[k-1] = L->data[k];
}
}

L->length--; //表长度减少

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n

链式存储

特色: 用一组任意的存储单元存储线性表的数据元素, 每个地址叫结点, 需要存储元素(数据域)和其后继元素的地址(指针域, 内部数据叫指针或链)\n\n*只有一个指针域 --> 单链表\n\n*头指针 -- > 指向头结点的指针(永不为空,即使链表为空); \n*头结点 --> 第一个结点 (不存储数据,数据域可以存放链表长度) ; \n*最后一个结点指向空(NULL); \n*空链表 -- > 有头结点和头指针, 没有其他结点, 直接指向NULL\n\n\nEG: ***********************************\n            typedef struct Node\n            {\n                ElemType data;      //数据域\n                struct Node* next;  //指针域\n            } Node;\n\n            typedef struct Node* LinkList;  //取Node*的别名为LinkList\n\n            //LinkList p;   p->data  p->next\n    ***********************************\n
\n

链式存储元素操作

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 获取list中的某个元素

Status GetElemL(LinkList L, int i, ElemType *e)
{
int j;
LinkList p; //p即为一个指针 指向链表

p = L->next;//使p指向当前链表的第一个结点
j = 1;

while( p && j < i)//判断p不为空 且没到要查询的结点 查到或者到头了就退出循环
{
p = p->next;
++j;
}

if(!p || j > i) //没找到
{
return ERROR;
}

*e = p->data;

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 2. 插入元素到list的指定的位置

Status ListInsertL(LinkList L, int i, ElemType e) //i插入的位置 e插入的数据
{
int j;
LinkList p, s;

p = L;
j = 1;

while(p && j<i)//循环遍历链表 使p指向一个一个结点直到最后(p结点为null)或者到达要插入的地方
{
p = p->next;
j++;
}

if(!p || j>i) // 判断要插入的位置存在
{
return ERROR;
}

s = (LinkList)malloc(sizeof(Node)); //生成新的结点 结点大小=sizeof(Node) 然后强转
s->data = e; //设置结点数据域

s->next = p->next; //把插入结点的上一个结点的指针赋给要插入的结点的指针域
p->next = s; //把插入结点的上一个结点的指针指向插入的结点

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// 3. 删除元素从list的指定的位置

Status ListRemove(LinkList L, int i, ElemType *e)// i是删除的位置 *e是删除的元素
{
int j;
LinkList p, q;

p = L; //先指向头结点, 要删除的结点从头结点指向的结点开始算起;即到最后要删除结点是p->next
j = 1;

while(p->next && j<i)//循环遍历链表 使p指向一个一个结点直到最后(p结点指针指向null)或者到达要插入的地方
{
p = p->next;
j++;
}

if(!(p->next) || j>i) // 判断要删除的结点存在
{
return ERROR;
}

q = p->next; //q结点是要删除的结点
p->next = q->next; //q结点指针域可能指向null(q是最后一个结点时候)

free(q); //释放空间

return OK;
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n

链式存储整表创建(建立单链表) (顺序存储 == 数组)

\n
\n

头插法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// 1. 头插法建立单链表:
// 把新加入的结点插入到链表头部, 把头结点指向新插入的结点(会使插入的结点顺序与原先设定的顺序颠倒)

void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;

// srand(time(0)); //初始化随机数

//这里L是指针 *L表示链表头结点
*L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点(可以放在函数外面)
(*L)->next = NULL; //设置头结点默认指向null

for(i=0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node)); //新建结点
p->data = rand() % 100 + 1; //随机数赋值到每个结点的数据域
p->next = (*L)->next; //把原来的头结点指针指向的地址赋值给新建的结点(因为要插入到头部)
(*L)->next = p; //更新头结点指针域
}
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n

尾插法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 尾插法建立单链表: 

void CreateListTail(LinkList *L, int n)
{
LinkList p, r;
int i;

// srand(time(0));

*L = (LinkList)malloc(sizeof(Node)); //初始化一个链表的头结点
r = *L; //使r指向生成的链表(当前只有头结点 所以r指向头结点) /*定义一个临时指针变量指向表头*/

for(i=0; i<n; i++)
{
p = (Node *)malloc(sizeof(Node)); //新建结点
p->data = rand() % 100 + 1; //随机数赋值到每个结点的数据域
r->next = p; //使r的指针域指向新的结点
r = p; //把新结点赋给r r指向链表的最后一个结点
}
r->next = NULL; //当全部插入的之后 要把最后一个节点的指针域指向NULL
}
\n
\n

测试结果:

\n
\n\"图片说明\"\n
\n
\n

链式存储整表删除

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Status ClearList(LinkList L)
{
LinkList p, q;

p = L->next;

while(p)
{
q = p->next;
free(p); //p指向每次都释放的地址
p = q;
}

L->next = NULL;

return OK;
}


## 静态链表: 用数组实现链表

#### 定义

#define MAXSIZE 1000
typedef struct
{
ElemType data; //数据
int cur; //游标(Cursor)
} Component, StaticLinkList[MAXSIZE];



#### 初始化

//初始化
void InitList(StaticLinkList space)
{
int i;
for(i=0; i < MAXSIZE-1; i++)
{
space[i].cur = i + 1;
}

space[MAXSIZE-1].cur = 1; //最后一个结点存放有数据的第一个节点下标
}

//返回链表长度
int ListLength(StaticLinkList L) {
int i, j = 0;
i = L[0].cur; //i指向第一个结点

while (i) //最后一个结点的指针域为0,结束循环
{
++j;
i = L[i].cur;
}
return j;
}
\n

插入新元素到静态链表

\"图片说明\"\n
\n

需要先获取一个空闲的链表位置, 插入数据,再把该位置插入到指定位置(其实就是更改cur, 使其链接起来)

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//申请分配一个空闲节点
int malloc_sl(StaticLinkList space) {//分配空闲节点
int i = space[0].cur;//总是取头结点之后的第一个空闲结点做分配,同时空闲链表非空,头结点做调整
if (space[0].cur) {
space[0].cur = space[i].cur;//空闲链表头结点调整指针域(把分配结点的cur存放在头结点)
}
return i;//返回申请到的空闲节点的数组下标
}

//插入数据到指定节点之前 i的前一个节点
void ListInsert(StaticLinkList L, int i, ElemType e) {//e是新插入的元素 i是要插入的位置(插入到第i个元素之前)
int j, k, l;

k = MAXSIZE - 1; //数组最后一个元素

if (i < 1 || i > ListLength(L)) {
return;
}

j = malloc_sl(L); //申请一个节点, j是下标
if (j) { // C里面非零就是真
L[j].data = e;//赋值到静态链表指定位置
for (l = 1; l <= i - 1; l++) {
k = L[k].cur; //目的是 通过循环获取到第(i-1)个元素的下标,其下标为k,其cur为i
}

L[j].cur = L[k].cur; //把下标为(i-1)的元素的cur赋值给第下标为j的元素的cur: 即把插入节点的\"指针\"指向要插入的节点之前(i)
L[k].cur = j; //把下标为(i-1)的元素的cur设置为新插入的节点; 原理和动态链表很相似
}
}
\n

静态链表删除元素

\"图片说明\"\n
\n

要先修改游标(把该节点的游标赋值到其上一个节点的游标) 然后在删除节点数据, 再把该节点接到后面的空闲节点上

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void free_sll(StaticLinkList L, int i) {
L[i].cur = L[0].cur; // 头结点中存放的是第一个空闲节点下标 把该下标赋值给删除的节点
L[0].cur = i; //把要删除的节点的下标赋值给头结点;
L[i].data = 0;
}

// 删除指定节点数据(下标减1的节点) L[i-1]
void ListDelete(StaticLinkList L, int i) {
int j, k;

k = MAXSIZE - 1;
if (i < 1 || i > ListLength(L)) {
return;
}

for (j = 1; j <= i - 1; j++) {
k = L[k].cur; //循环得到要删除的结点的前一个节点(不是物理的前一个,而是cur是i-1的节点)
}

j = L[k].cur; //获取要删除的结点的cur赋值到j
L[k].cur = L[j].cur;//把j赋值给该节点

free_sll(L, j);//\"释放\"节点数据
}
\n

循环链表

定义: 最后一个结点的指针域指向头结点,整个链表形成一个环

\n

判断空链表的条件是 head == head->next;

\n
\n"},{"title":"普通链表实例(全)","comments":1,"description":null,"_content":"\n> 链表的插入,删除,遍历等功能的实例\n \n> 工程一共包含4个文件\n1. Entity.h :声明表的元素的类型。可以是基本数据类型也可以是结构体\n2. ChainList.h :定义表结构体,声明全局的宏定义,函数的声明\n3. ChainList.c :具体的函数实现\n4. main.c : 测试文件\n\n> 参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html\n> 参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/\n\n\n \n> #### Entity.h\n\n typedef struct{\n char key[15]; //结点的关键字\n char name[20];\n int age;\n } DATA; //定义结点类型 可定义为简单类型或者结构体\n\n \n> #### ChainList.h \n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \"Entity.h\"\n \n typedef struct Node{\n DATA data; //数据域\n struct Node *next; //指针域,指向下一个结点的地址\n } ChainListType;\n \n ChainListType *ChainListAddEnd(ChainListType *head, DATA data); //添加结点到链表结尾\n ChainListType *ChainListAddFirst(ChainListType *head, DATA data); //添加结点到头部\n ChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data);//把数据插入链表(插入到某个关键字之后)\n ChainListType *ChainListFind(ChainListType *head, char *key); //按关键字查找\n int ChainListDelete(ChainListType *head, char *key); //删除指定关键字的结点\n int ChainListLength(ChainListType *head); //获取链表结点数量\n\n\n\n> #### ChainList.c\n \n \n #include \n #include \n #include \"ChainList.h\"\n \n /*添加结点到链表结尾*/\n ChainListType *ChainListAddEnd(ChainListType *head, DATA data){\n ChainListType *node, *h; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n \n if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n //如果失败\n printf(\"申请内存失败\\n\");\n return NULL;\n }\n //分配成功\n node->data = data; //设置数据域\n node->next = NULL; //设置指针域指向空(这是一个结点)\n \n //把新加的结点连接到链表\n if(head == NULL){ //如果头结点为空 表示没有实际结点\n head = node; //头结点指向这个新结点\n \n printf(\"|||||\");\n ChainListLength(head);\n printf(\"|||||\");\n \n return head;\n }else{ //头结点不为空 遍历到达当前链表的最后一个结点\n h = head;\n while(h->next != NULL){\n h = h->next;\n }\n h->next = node;//到达最后一个结点 赋值\n \n return head;\n }\n }\n \n /*添加结点到头部*/\n ChainListType *ChainListAddFirst(ChainListType *head, DATA data){\n ChainListType *node; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n \n if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n //如果失败\n printf(\"申请内存失败\\n\");\n return NULL;\n }\n //分配成功\n node->data = data; //设置数据域\n node->next = head; //设置指针域指向原来头指针指向的地址\n head = node; //头结点指向新增结点\n \n return head;\n }\n \n /*把数据插入链表(插入到某个关键字之后)*/\n ChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data){\n ChainListType *node, *node1; //临时变量 用于保存新结点的地址\n \n if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n //如果失败\n printf(\"申请内存失败\\n\");\n return NULL;\n }\n //分配成功\n node->data = data; //设置数据域\n node1 = ChainListFind(head, findKey); //查找指定关键字的结点\n if(node1){ //如果找到该结点\n node->next = node1->next; //把找到的结点的下一个结点的地址赋值给新结点\n node1->next = node; //把找到的结点的指针域指向新结点\n }else{\n free(node); //释放内存\n printf(\"没有找到结点\");\n }\n \n return head;\n }\n \n /*按关键字查找*/\n ChainListType *ChainListFind(ChainListType *head, char *key){\n ChainListType *h;\n h = head;\n while(h){\n if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n return h;\n }\n h = h->next;\n }\n \n return NULL;\n }\n \n /*删除指定关键字的结点*/\n int ChainListDelete(ChainListType *head, char *key){\n ChainListType *node, *h; //h指向循环当前结点 node指向h的前一个结点\n node = h = head;\n while(h){\n if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n node->next = h->next;\n free(h); //释放 删除\n return 1;\n }else{\n node = h; //把h赋值给node\n h = h->next; //h指向h的下一个结点\n }\n }\n \n return 0;\n }\n \n /*获取链表结点数量*/\n int ChainListLength(ChainListType *head){\n ChainListType *h;\n int i = 0;\n h = head;\n \n if(h == NULL){\n printf(\"没有数据!!!\");\n return 0;\n }\n \n while(h){\n h = h->next;\n i++;\n }\n \n return i;\n }\n\n\n\n> #### main.c\n\n \n /* 测试文件:调用测试函数*/\n #include \n #include \"ChainList.h\"\n \n /*遍历链表的数据*/\n void ChainListAll(ChainListType *head){\n ChainListType *h;\n h = head;\n printf(\"链表所有的数据:\\n\");\n while(h){ //判断当前结点的存在\n printf(\"%s %s %d \\t\", h->data.key, h->data.name, h->data.age);\n h = h->next;\n }\n \n return;\n }\n \n int main(void){\n ChainListType *node, *head = NULL;\n DATA data;\n int k, i;\n char key[15];\n \n while(1){\n fflush(stdin);\n printf(\"\\n\\n输入操作\\n1.插入到末尾\\t2.内容查询\\t3.插入到头部\\t4.插入到指定结点之后\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:\");\n scanf(\"%d\", &k);\n if(k == 8){\n break;\n }\n switch(k){\n case 1:\n printf(\"插入到末尾:输入元素内容:\");\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n head = ChainListAddEnd(head, data);\n printf(\"插入的元素为:(%s %s %d) \\n\", head->data.key, head->data.name, head->data.age);\n break;\n case 2:\n printf(\"输入元素key:\");\n scanf(\"%s\", &key);\n node = ChainListFind(head, &key);\n printf(\"元素为:(%s %s %d) \\n\", node->data.key, node->data.name, node->data.age);\n break;\n case 3:\n printf(\"插入到头部:输入元素内容:\");\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n head = ChainListAddFirst(head, data);\n printf(\"插入的元素为:(%s %s %d) \\n\", head->data.key, head->data.name, head->data.age);\n break;\n case 4:\n printf(\"插入到指定结点之后:输入位置元素key和元素内容:\");\n scanf(\"%s %s %s %d\", &key, &data.key, &data.name, &data.age);\n head = ChainListInsert(head, &key, data);\n printf(\"插入的元素为:(%s %s %d) \\n\", head->data.key, head->data.name, head->data.age);\n break;\n case 5:\n printf(\"输入要删除的元素key:\");\n scanf(\"%s\", &key);\n i = ChainListDelete(head, &key);\n if(i == 1){\n printf(\"删除成功\\n\");\n }else{\n printf(\"删除失败\\n\");\n }\n break;\n case 6:\n printf(\"-----%d------\\n\", ChainListLength(head));\n break;\n case 7:\n ChainListAll(head);\n break;\n }\n }\n \n return 0;\n }\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170812_lb.md","raw":"---\ntitle: 普通链表实例(全)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> 链表的插入,删除,遍历等功能的实例\n \n> 工程一共包含4个文件\n1. Entity.h :声明表的元素的类型。可以是基本数据类型也可以是结构体\n2. ChainList.h :定义表结构体,声明全局的宏定义,函数的声明\n3. ChainList.c :具体的函数实现\n4. main.c : 测试文件\n\n> 参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html\n> 参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/\n\n\n \n> #### Entity.h\n\n typedef struct{\n char key[15]; //结点的关键字\n char name[20];\n int age;\n } DATA; //定义结点类型 可定义为简单类型或者结构体\n\n \n> #### ChainList.h \n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \"Entity.h\"\n \n typedef struct Node{\n DATA data; //数据域\n struct Node *next; //指针域,指向下一个结点的地址\n } ChainListType;\n \n ChainListType *ChainListAddEnd(ChainListType *head, DATA data); //添加结点到链表结尾\n ChainListType *ChainListAddFirst(ChainListType *head, DATA data); //添加结点到头部\n ChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data);//把数据插入链表(插入到某个关键字之后)\n ChainListType *ChainListFind(ChainListType *head, char *key); //按关键字查找\n int ChainListDelete(ChainListType *head, char *key); //删除指定关键字的结点\n int ChainListLength(ChainListType *head); //获取链表结点数量\n\n\n\n> #### ChainList.c\n \n \n #include \n #include \n #include \"ChainList.h\"\n \n /*添加结点到链表结尾*/\n ChainListType *ChainListAddEnd(ChainListType *head, DATA data){\n ChainListType *node, *h; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n \n if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n //如果失败\n printf(\"申请内存失败\\n\");\n return NULL;\n }\n //分配成功\n node->data = data; //设置数据域\n node->next = NULL; //设置指针域指向空(这是一个结点)\n \n //把新加的结点连接到链表\n if(head == NULL){ //如果头结点为空 表示没有实际结点\n head = node; //头结点指向这个新结点\n \n printf(\"|||||\");\n ChainListLength(head);\n printf(\"|||||\");\n \n return head;\n }else{ //头结点不为空 遍历到达当前链表的最后一个结点\n h = head;\n while(h->next != NULL){\n h = h->next;\n }\n h->next = node;//到达最后一个结点 赋值\n \n return head;\n }\n }\n \n /*添加结点到头部*/\n ChainListType *ChainListAddFirst(ChainListType *head, DATA data){\n ChainListType *node; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n \n if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n //如果失败\n printf(\"申请内存失败\\n\");\n return NULL;\n }\n //分配成功\n node->data = data; //设置数据域\n node->next = head; //设置指针域指向原来头指针指向的地址\n head = node; //头结点指向新增结点\n \n return head;\n }\n \n /*把数据插入链表(插入到某个关键字之后)*/\n ChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data){\n ChainListType *node, *node1; //临时变量 用于保存新结点的地址\n \n if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n //如果失败\n printf(\"申请内存失败\\n\");\n return NULL;\n }\n //分配成功\n node->data = data; //设置数据域\n node1 = ChainListFind(head, findKey); //查找指定关键字的结点\n if(node1){ //如果找到该结点\n node->next = node1->next; //把找到的结点的下一个结点的地址赋值给新结点\n node1->next = node; //把找到的结点的指针域指向新结点\n }else{\n free(node); //释放内存\n printf(\"没有找到结点\");\n }\n \n return head;\n }\n \n /*按关键字查找*/\n ChainListType *ChainListFind(ChainListType *head, char *key){\n ChainListType *h;\n h = head;\n while(h){\n if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n return h;\n }\n h = h->next;\n }\n \n return NULL;\n }\n \n /*删除指定关键字的结点*/\n int ChainListDelete(ChainListType *head, char *key){\n ChainListType *node, *h; //h指向循环当前结点 node指向h的前一个结点\n node = h = head;\n while(h){\n if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n node->next = h->next;\n free(h); //释放 删除\n return 1;\n }else{\n node = h; //把h赋值给node\n h = h->next; //h指向h的下一个结点\n }\n }\n \n return 0;\n }\n \n /*获取链表结点数量*/\n int ChainListLength(ChainListType *head){\n ChainListType *h;\n int i = 0;\n h = head;\n \n if(h == NULL){\n printf(\"没有数据!!!\");\n return 0;\n }\n \n while(h){\n h = h->next;\n i++;\n }\n \n return i;\n }\n\n\n\n> #### main.c\n\n \n /* 测试文件:调用测试函数*/\n #include \n #include \"ChainList.h\"\n \n /*遍历链表的数据*/\n void ChainListAll(ChainListType *head){\n ChainListType *h;\n h = head;\n printf(\"链表所有的数据:\\n\");\n while(h){ //判断当前结点的存在\n printf(\"%s %s %d \\t\", h->data.key, h->data.name, h->data.age);\n h = h->next;\n }\n \n return;\n }\n \n int main(void){\n ChainListType *node, *head = NULL;\n DATA data;\n int k, i;\n char key[15];\n \n while(1){\n fflush(stdin);\n printf(\"\\n\\n输入操作\\n1.插入到末尾\\t2.内容查询\\t3.插入到头部\\t4.插入到指定结点之后\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:\");\n scanf(\"%d\", &k);\n if(k == 8){\n break;\n }\n switch(k){\n case 1:\n printf(\"插入到末尾:输入元素内容:\");\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n head = ChainListAddEnd(head, data);\n printf(\"插入的元素为:(%s %s %d) \\n\", head->data.key, head->data.name, head->data.age);\n break;\n case 2:\n printf(\"输入元素key:\");\n scanf(\"%s\", &key);\n node = ChainListFind(head, &key);\n printf(\"元素为:(%s %s %d) \\n\", node->data.key, node->data.name, node->data.age);\n break;\n case 3:\n printf(\"插入到头部:输入元素内容:\");\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n head = ChainListAddFirst(head, data);\n printf(\"插入的元素为:(%s %s %d) \\n\", head->data.key, head->data.name, head->data.age);\n break;\n case 4:\n printf(\"插入到指定结点之后:输入位置元素key和元素内容:\");\n scanf(\"%s %s %s %d\", &key, &data.key, &data.name, &data.age);\n head = ChainListInsert(head, &key, data);\n printf(\"插入的元素为:(%s %s %d) \\n\", head->data.key, head->data.name, head->data.age);\n break;\n case 5:\n printf(\"输入要删除的元素key:\");\n scanf(\"%s\", &key);\n i = ChainListDelete(head, &key);\n if(i == 1){\n printf(\"删除成功\\n\");\n }else{\n printf(\"删除失败\\n\");\n }\n break;\n case 6:\n printf(\"-----%d------\\n\", ChainListLength(head));\n break;\n case 7:\n ChainListAll(head);\n break;\n }\n }\n \n return 0;\n }\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170812_lb","published":1,"date":"2020-01-15T05:50:39.625Z","updated":"2021-03-10T13:50:15.298Z","layout":"post","photos":[],"link":"","_id":"ckm3invds007624ujhecbghti","content":"
\n

链表的插入,删除,遍历等功能的实例

\n
\n
\n

工程一共包含4个文件

\n
    \n
  1. Entity.h :声明表的元素的类型。可以是基本数据类型也可以是结构体
  2. \n
  3. ChainList.h :定义表结构体,声明全局的宏定义,函数的声明
  4. \n
  5. ChainList.c :具体的函数实现
  6. \n
  7. main.c : 测试文件
  8. \n
\n
\n
\n

参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html
参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/

\n
\n\n
\n

Entity.h

\n
typedef struct{\n    char key[15];   //结点的关键字\n    char name[20];\n    int age;\n} DATA; //定义结点类型 可定义为简单类型或者结构体\n
\n

ChainList.h

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include "Entity.h"\n\ntypedef struct Node{\n    DATA data;          //数据域\n    struct Node *next;  //指针域,指向下一个结点的地址\n} ChainListType;\n\nChainListType *ChainListAddEnd(ChainListType *head, DATA data);     //添加结点到链表结尾\nChainListType *ChainListAddFirst(ChainListType *head, DATA data);   //添加结点到头部\nChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data);//把数据插入链表(插入到某个关键字之后)\nChainListType *ChainListFind(ChainListType *head, char *key);       //按关键字查找\nint ChainListDelete(ChainListType *head, char *key);                //删除指定关键字的结点\nint ChainListLength(ChainListType *head);                           //获取链表结点数量\n
\n

ChainList.c

\n
#include <string.h>\n#include <malloc.h>\n#include "ChainList.h"\n\n/*添加结点到链表结尾*/\nChainListType *ChainListAddEnd(ChainListType *head, DATA data){\n    ChainListType *node, *h; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n\n    if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n        //如果失败\n        printf("申请内存失败\\n");\n        return NULL;\n    }\n    //分配成功\n    node->data = data;  //设置数据域\n    node->next = NULL;  //设置指针域指向空(这是一个结点)\n\n    //把新加的结点连接到链表\n    if(head == NULL){   //如果头结点为空 表示没有实际结点\n        head = node; //头结点指向这个新结点\n\n        printf("|||||");\n        ChainListLength(head);\n        printf("|||||");\n\n        return head;\n    }else{              //头结点不为空 遍历到达当前链表的最后一个结点\n        h = head;\n        while(h->next != NULL){\n            h = h->next;\n        }\n        h->next = node;//到达最后一个结点 赋值\n\n        return head;\n    }\n}\n\n/*添加结点到头部*/\nChainListType *ChainListAddFirst(ChainListType *head, DATA data){\n    ChainListType *node; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n\n    if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n        //如果失败\n        printf("申请内存失败\\n");\n        return NULL;\n    }\n    //分配成功\n    node->data = data;  //设置数据域\n    node->next = head;  //设置指针域指向原来头指针指向的地址\n    head = node;        //头结点指向新增结点\n\n    return head;\n}\n\n/*把数据插入链表(插入到某个关键字之后)*/\nChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data){\n    ChainListType *node, *node1; //临时变量 用于保存新结点的地址\n\n    if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n        //如果失败\n        printf("申请内存失败\\n");\n        return NULL;\n    }\n    //分配成功\n    node->data = data;                      //设置数据域\n    node1 = ChainListFind(head, findKey);   //查找指定关键字的结点\n    if(node1){                              //如果找到该结点\n        node->next = node1->next;           //把找到的结点的下一个结点的地址赋值给新结点\n        node1->next = node;                 //把找到的结点的指针域指向新结点\n    }else{\n        free(node);                         //释放内存\n        printf("没有找到结点");\n    }\n\n    return head;\n}\n\n/*按关键字查找*/\nChainListType *ChainListFind(ChainListType *head, char *key){\n    ChainListType *h;\n    h = head;\n    while(h){\n        if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n            return h;\n        }\n        h = h->next;\n    }\n\n    return NULL;\n}\n\n/*删除指定关键字的结点*/\nint ChainListDelete(ChainListType *head, char *key){\n    ChainListType *node, *h; //h指向循环当前结点 node指向h的前一个结点\n    node = h = head;\n    while(h){\n        if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n            node->next = h->next;\n            free(h);        //释放 删除\n            return 1;\n        }else{\n            node = h;       //把h赋值给node\n            h = h->next;    //h指向h的下一个结点\n        }\n    }\n\n    return 0;\n}\n\n/*获取链表结点数量*/\nint ChainListLength(ChainListType *head){\n    ChainListType *h;\n    int i = 0;\n    h = head;\n\n    if(h == NULL){\n        printf("没有数据!!!");\n        return 0;\n    }\n\n    while(h){\n        h = h->next;\n        i++;\n    }\n\n    return i;\n}\n
\n

main.c

\n
/* 测试文件:调用测试函数*/\n#include <stdio.h>\n#include "ChainList.h"\n\n/*遍历链表的数据*/\nvoid ChainListAll(ChainListType *head){\n    ChainListType *h;\n    h = head;\n    printf("链表所有的数据:\\n");\n    while(h){   //判断当前结点的存在\n        printf("%s %s %d \\t", h->data.key, h->data.name, h->data.age);\n        h = h->next;\n    }\n\n    return;\n}\n\nint main(void){\n    ChainListType *node, *head = NULL;\n    DATA data;\n    int k, i;\n    char key[15];\n\n    while(1){\n        fflush(stdin);\n        printf("\\n\\n输入操作\\n1.插入到末尾\\t2.内容查询\\t3.插入到头部\\t4.插入到指定结点之后\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:");\n        scanf("%d", &k);\n        if(k == 8){\n            break;\n        }\n        switch(k){\n            case 1:\n                printf("插入到末尾:输入元素内容:");\n                scanf("%s %s %d", &data.key, &data.name, &data.age);\n                head = ChainListAddEnd(head, data);\n                printf("插入的元素为:(%s %s %d) \\n", head->data.key, head->data.name, head->data.age);\n                break;\n            case 2:\n                printf("输入元素key:");\n                scanf("%s", &key);\n                node = ChainListFind(head, &key);\n                printf("元素为:(%s %s %d) \\n", node->data.key, node->data.name, node->data.age);\n                break;\n            case 3:\n                printf("插入到头部:输入元素内容:");\n                scanf("%s %s %d", &data.key, &data.name, &data.age);\n                head = ChainListAddFirst(head, data);\n                printf("插入的元素为:(%s %s %d) \\n", head->data.key, head->data.name, head->data.age);\n                break;\n            case 4:\n                printf("插入到指定结点之后:输入位置元素key和元素内容:");\n                scanf("%s %s %s %d", &key, &data.key, &data.name, &data.age);\n                head = ChainListInsert(head, &key, data);\n                printf("插入的元素为:(%s %s %d) \\n", head->data.key, head->data.name, head->data.age);\n                break;\n            case 5:\n                printf("输入要删除的元素key:");\n                scanf("%s", &key);\n                i = ChainListDelete(head, &key);\n                if(i == 1){\n                    printf("删除成功\\n");\n                }else{\n                    printf("删除失败\\n");\n                }\n                break;\n            case 6:\n                printf("-----%d------\\n", ChainListLength(head));\n                break;\n            case 7:\n                ChainListAll(head);\n                 break;\n        }\n    }\n\n    return 0;\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

链表的插入,删除,遍历等功能的实例

\n
\n
\n

工程一共包含4个文件

\n
    \n
  1. Entity.h :声明表的元素的类型。可以是基本数据类型也可以是结构体
  2. \n
  3. ChainList.h :定义表结构体,声明全局的宏定义,函数的声明
  4. \n
  5. ChainList.c :具体的函数实现
  6. \n
  7. main.c : 测试文件
  8. \n
\n
\n
\n

参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html
参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/

\n
","more":"
\n

Entity.h

\n
typedef struct{\n    char key[15];   //结点的关键字\n    char name[20];\n    int age;\n} DATA; //定义结点类型 可定义为简单类型或者结构体\n
\n

ChainList.h

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include "Entity.h"\n\ntypedef struct Node{\n    DATA data;          //数据域\n    struct Node *next;  //指针域,指向下一个结点的地址\n} ChainListType;\n\nChainListType *ChainListAddEnd(ChainListType *head, DATA data);     //添加结点到链表结尾\nChainListType *ChainListAddFirst(ChainListType *head, DATA data);   //添加结点到头部\nChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data);//把数据插入链表(插入到某个关键字之后)\nChainListType *ChainListFind(ChainListType *head, char *key);       //按关键字查找\nint ChainListDelete(ChainListType *head, char *key);                //删除指定关键字的结点\nint ChainListLength(ChainListType *head);                           //获取链表结点数量\n
\n

ChainList.c

\n
#include <string.h>\n#include <malloc.h>\n#include "ChainList.h"\n\n/*添加结点到链表结尾*/\nChainListType *ChainListAddEnd(ChainListType *head, DATA data){\n    ChainListType *node, *h; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n\n    if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n        //如果失败\n        printf("申请内存失败\\n");\n        return NULL;\n    }\n    //分配成功\n    node->data = data;  //设置数据域\n    node->next = NULL;  //设置指针域指向空(这是一个结点)\n\n    //把新加的结点连接到链表\n    if(head == NULL){   //如果头结点为空 表示没有实际结点\n        head = node; //头结点指向这个新结点\n\n        printf("|||||");\n        ChainListLength(head);\n        printf("|||||");\n\n        return head;\n    }else{              //头结点不为空 遍历到达当前链表的最后一个结点\n        h = head;\n        while(h->next != NULL){\n            h = h->next;\n        }\n        h->next = node;//到达最后一个结点 赋值\n\n        return head;\n    }\n}\n\n/*添加结点到头部*/\nChainListType *ChainListAddFirst(ChainListType *head, DATA data){\n    ChainListType *node; //临时变量 用于保存新结点的地址和链表当前(头结点和循环时候的)结点的地址(即head)\n\n    if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n        //如果失败\n        printf("申请内存失败\\n");\n        return NULL;\n    }\n    //分配成功\n    node->data = data;  //设置数据域\n    node->next = head;  //设置指针域指向原来头指针指向的地址\n    head = node;        //头结点指向新增结点\n\n    return head;\n}\n\n/*把数据插入链表(插入到某个关键字之后)*/\nChainListType *ChainListInsert(ChainListType *head, char *findKey, DATA data){\n    ChainListType *node, *node1; //临时变量 用于保存新结点的地址\n\n    if(! (node = (ChainListType *)malloc(sizeof(ChainListType)))){//申请赋予内存地址用来保存新结点\n        //如果失败\n        printf("申请内存失败\\n");\n        return NULL;\n    }\n    //分配成功\n    node->data = data;                      //设置数据域\n    node1 = ChainListFind(head, findKey);   //查找指定关键字的结点\n    if(node1){                              //如果找到该结点\n        node->next = node1->next;           //把找到的结点的下一个结点的地址赋值给新结点\n        node1->next = node;                 //把找到的结点的指针域指向新结点\n    }else{\n        free(node);                         //释放内存\n        printf("没有找到结点");\n    }\n\n    return head;\n}\n\n/*按关键字查找*/\nChainListType *ChainListFind(ChainListType *head, char *key){\n    ChainListType *h;\n    h = head;\n    while(h){\n        if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n            return h;\n        }\n        h = h->next;\n    }\n\n    return NULL;\n}\n\n/*删除指定关键字的结点*/\nint ChainListDelete(ChainListType *head, char *key){\n    ChainListType *node, *h; //h指向循环当前结点 node指向h的前一个结点\n    node = h = head;\n    while(h){\n        if(strcmp(h->data.key, key) == 0){ //字符串对比函数 相同则返回0\n            node->next = h->next;\n            free(h);        //释放 删除\n            return 1;\n        }else{\n            node = h;       //把h赋值给node\n            h = h->next;    //h指向h的下一个结点\n        }\n    }\n\n    return 0;\n}\n\n/*获取链表结点数量*/\nint ChainListLength(ChainListType *head){\n    ChainListType *h;\n    int i = 0;\n    h = head;\n\n    if(h == NULL){\n        printf("没有数据!!!");\n        return 0;\n    }\n\n    while(h){\n        h = h->next;\n        i++;\n    }\n\n    return i;\n}\n
\n

main.c

\n
/* 测试文件:调用测试函数*/\n#include <stdio.h>\n#include "ChainList.h"\n\n/*遍历链表的数据*/\nvoid ChainListAll(ChainListType *head){\n    ChainListType *h;\n    h = head;\n    printf("链表所有的数据:\\n");\n    while(h){   //判断当前结点的存在\n        printf("%s %s %d \\t", h->data.key, h->data.name, h->data.age);\n        h = h->next;\n    }\n\n    return;\n}\n\nint main(void){\n    ChainListType *node, *head = NULL;\n    DATA data;\n    int k, i;\n    char key[15];\n\n    while(1){\n        fflush(stdin);\n        printf("\\n\\n输入操作\\n1.插入到末尾\\t2.内容查询\\t3.插入到头部\\t4.插入到指定结点之后\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:");\n        scanf("%d", &k);\n        if(k == 8){\n            break;\n        }\n        switch(k){\n            case 1:\n                printf("插入到末尾:输入元素内容:");\n                scanf("%s %s %d", &data.key, &data.name, &data.age);\n                head = ChainListAddEnd(head, data);\n                printf("插入的元素为:(%s %s %d) \\n", head->data.key, head->data.name, head->data.age);\n                break;\n            case 2:\n                printf("输入元素key:");\n                scanf("%s", &key);\n                node = ChainListFind(head, &key);\n                printf("元素为:(%s %s %d) \\n", node->data.key, node->data.name, node->data.age);\n                break;\n            case 3:\n                printf("插入到头部:输入元素内容:");\n                scanf("%s %s %d", &data.key, &data.name, &data.age);\n                head = ChainListAddFirst(head, data);\n                printf("插入的元素为:(%s %s %d) \\n", head->data.key, head->data.name, head->data.age);\n                break;\n            case 4:\n                printf("插入到指定结点之后:输入位置元素key和元素内容:");\n                scanf("%s %s %s %d", &key, &data.key, &data.name, &data.age);\n                head = ChainListInsert(head, &key, data);\n                printf("插入的元素为:(%s %s %d) \\n", head->data.key, head->data.name, head->data.age);\n                break;\n            case 5:\n                printf("输入要删除的元素key:");\n                scanf("%s", &key);\n                i = ChainListDelete(head, &key);\n                if(i == 1){\n                    printf("删除成功\\n");\n                }else{\n                    printf("删除失败\\n");\n                }\n                break;\n            case 6:\n                printf("-----%d------\\n", ChainListLength(head));\n                break;\n            case 7:\n                ChainListAll(head);\n                 break;\n        }\n    }\n\n    return 0;\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"顺序表实例(全)","comments":1,"description":null,"_content":"\n> 顺序表的初始化,增加,插入,删除等功能的实例\n \n> 工程一共包含4个文件\n1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体\n2. SeqList.h :定义线性表结构体,声明全局的宏定义,函数的声明\n3. SeqList.c :具体的函数实现\n4. main.c : 测试文件\n\n> 参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html\n> 参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/\n\n\n \n> #### Entity.h\n\n typedef struct{\n char key[15]; //结点的关键字\n char name[20];\n int age;\n } DATA; //定义结点类型 可定义为简单类型或者结构体\n\n \n> #### SeqList.h \n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \n #include \"Entity.h\"\n #define MAXSIZE 100 //定义线性表最大长度\n \n typedef struct {\n DATA ListData[MAXSIZE + 1]; //保存顺序表的数组(真正的数据从下标为1的位置开始)\n int ListLen; //顺序表结点个数(已存结点);默认为0,表示没有数据\n } SeqListType;\n \n void SeqListInit(SeqListType *SL); //初始化顺序表\n int SeqListLength(SeqListType *SL); // 返回顺序表的元素数量\n int SeqListAdd(SeqListType *SL, DATA data); // 向顺序表中添加元素\n int SeqListInsert(SeqListType *SL, int n, DATA data); // 向顺序表中插入元素\n int SeqListDelete(SeqListType *SL, int n); // 删除顺序表中的数据\n DATA *SeqListFindByNum(SeqListType *SL, int n); // 根据序号返回元素\n int SeqListFindByCont(SeqListType *SL, char *key); // 按关键字查找\n int SeqListAll(SeqListType *SL); // 遍历顺序表\n\n\n> #### SeqList.c\n \n \n /*函数文件:具体的函数实现代码*/\n \n #include \"SeqList.h\"\n \n /* 初始化顺序表 */\n void SeqListInit(SeqListType *SL){\n SL->ListLen = 0;\n }\n \n /* 返回顺序表元素数量 */\n int SeqListLength(SeqListType *SL){\n return (SL->ListLen);\n }\n \n /* 添加元素到顺序表尾 */\n int SeqListAdd(SeqListType *SL, DATA data){\n if(SL->ListLen >= MAXSIZE){ // 顺序表已满\n printf(\"顺序表已经满了 不能再添加\");\n return 0; //返回失败\n }\n \n SL->ListData[++SL->ListLen] = data; // 把数据插入到下标为(ListLen+1)的位置\n \n return 1;//返回成功\n }\n \n /* 插入元素到顺序表指定位置 */\n int SeqListInsert(SeqListType *SL, int n, DATA data){\n int i;\n \n if(SL->ListLen >= MAXSIZE){ // 顺序表已满\n printf(\"顺序表已经满了 不能再插入\\n\");\n return 0;\n }\n \n if(n < 1 || n > SL->ListLen){\n printf(\"要插入的位置错误\\n\");\n return 0;\n }\n \n for(i = SL->ListLen; i>=n; i--){ //移动要插入数据的后面的数据\n SL->ListData[i+1] = SL->ListData[i];\n }\n SL->ListData[n] = data; //插入数据\n SL->ListLen++; //数据个数加一\n \n return 1;\n }\n \n int SeqListDelete(SeqListType *SL, int n){\n int i;\n if(n < 1 || n > SL->ListLen+1){\n printf(\"结点错误 不能删除\\n\");\n return 0;\n }\n \n for(i=n; iListLen; i++){ //移动要删除数据的后面的数据\n SL->ListData[i] = SL->ListData[i + 1];\n }\n SL->ListLen--;\n \n return 1;\n }\n \n DATA *SeqListFindByNum(SeqListType *SL, int n){\n if(n < 1 || n > SL->ListLen+1){\n printf(\"序号错误 获取失败\");\n return NULL;\n }\n \n return &(SL->ListData[n]); // 返回指针增加通用性\n }\n \n int SeqListFindByCont(SeqListType *SL, char *key){\n \n int i;\n for(i = 0; i <= SL->ListLen; i++){\n if(strcmp(SL->ListData[i].key, key) == 0){\n return i;\n }\n }\n \n return 0; //遍历没有找到\n }\n\n\n> #### main.c\n\n /* 测试文件:调用测试函数*/\n #include \n #include \"SeqList.h\"\n \n /*遍历顺序表中结点*/\n int SeqListAll(SeqListType *SL){\n int i;\n for(i = 0; i <= SL->ListLen; i++){\n //输出中第一个是0, 即下标为0的位置的存储的数据\n printf(\"(%s %s %d) \\n\", SL->ListData[i].key, SL->ListData[i].name, SL->ListData[i].age);\n }\n \n return 0;\n }\n \n int main(void){\n int i, k;\n SeqListType SL; //定义顺序表变量\n DATA data, *data1; //定义结点保存数据类型变量和指针变量\n char key[15]; //保存关键字\n \n SeqListInit(&SL); //初始化数据表\n \n do{\n printf(\"请输入学号 姓名 年龄: \");\n fflush(stdin); //清空输入缓冲区\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n if(data.age){ //年龄不是0 退出循环\n if(!SeqListAdd(&SL, data)){//添加元素到顺序表\n break; //当添加失败 退出循环\n }\n }else{ //当年龄为0 退出循环\n break;\n }\n }while(1);\n \n printf(\"顺序表为: \\n\");\n SeqListAll(&SL);\n \n while(1){\n fflush(stdin);\n printf(\"\\n\\n输入操作\\n1.获取结点位置元素\\t2.内容查询\\t3.添加\\t4.插入\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:\");\n scanf(\"%d\", &k);\n if(k == 8){\n break;\n }\n switch(k){\n case 1:\n printf(\"输入元素位置:\");\n scanf(\"%d\", &i);\n data1 = SeqListFindByNum(&SL, i);\n printf(\"元素为:(%s %s %d) \\n\", data1->key, data1->name, data1->age);\n break;\n case 2:\n printf(\"输入元素key(学号):\");\n scanf(\"%s\", &key);\n i = SeqListFindByCont(&SL, key);\n if(i == 0){\n printf(\"没有找到对应元素!\");\n break;\n }\n data1 = SeqListFindByNum(&SL, i);\n printf(\"位置为: %d ,元素为:(%s %s %d) \\n\", i, data1->key, data1->name, data1->age);\n break;\n case 3:\n printf(\"输入元素内容:\");\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n SeqListAdd(&SL, data);\n break;\n case 4:\n printf(\"输入位置和元素内容:\");\n scanf(\"%d %s %s %d\", &i, &data.key, &data.name, &data.age);\n SeqListInsert(&SL, i, data);\n break;\n case 5:\n printf(\"输入要删除的位置:\");\n scanf(\"%d\", &i);\n SeqListDelete(&SL, i);\n break;\n case 6:\n printf(\"-----%d------\\n\", SeqListLength(&SL));\n break;\n case 7:\n SeqListAll(&SL);\n break;\n }\n }\n \n return 0;\n }\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170811_xxb.md","raw":"---\ntitle: 顺序表实例(全)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> 顺序表的初始化,增加,插入,删除等功能的实例\n \n> 工程一共包含4个文件\n1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体\n2. SeqList.h :定义线性表结构体,声明全局的宏定义,函数的声明\n3. SeqList.c :具体的函数实现\n4. main.c : 测试文件\n\n> 参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html\n> 参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/\n\n\n \n> #### Entity.h\n\n typedef struct{\n char key[15]; //结点的关键字\n char name[20];\n int age;\n } DATA; //定义结点类型 可定义为简单类型或者结构体\n\n \n> #### SeqList.h \n\n /*\n 头文件:数据结构的定义和操作原型\n \n */\n #include \n #include \n #include \"Entity.h\"\n #define MAXSIZE 100 //定义线性表最大长度\n \n typedef struct {\n DATA ListData[MAXSIZE + 1]; //保存顺序表的数组(真正的数据从下标为1的位置开始)\n int ListLen; //顺序表结点个数(已存结点);默认为0,表示没有数据\n } SeqListType;\n \n void SeqListInit(SeqListType *SL); //初始化顺序表\n int SeqListLength(SeqListType *SL); // 返回顺序表的元素数量\n int SeqListAdd(SeqListType *SL, DATA data); // 向顺序表中添加元素\n int SeqListInsert(SeqListType *SL, int n, DATA data); // 向顺序表中插入元素\n int SeqListDelete(SeqListType *SL, int n); // 删除顺序表中的数据\n DATA *SeqListFindByNum(SeqListType *SL, int n); // 根据序号返回元素\n int SeqListFindByCont(SeqListType *SL, char *key); // 按关键字查找\n int SeqListAll(SeqListType *SL); // 遍历顺序表\n\n\n> #### SeqList.c\n \n \n /*函数文件:具体的函数实现代码*/\n \n #include \"SeqList.h\"\n \n /* 初始化顺序表 */\n void SeqListInit(SeqListType *SL){\n SL->ListLen = 0;\n }\n \n /* 返回顺序表元素数量 */\n int SeqListLength(SeqListType *SL){\n return (SL->ListLen);\n }\n \n /* 添加元素到顺序表尾 */\n int SeqListAdd(SeqListType *SL, DATA data){\n if(SL->ListLen >= MAXSIZE){ // 顺序表已满\n printf(\"顺序表已经满了 不能再添加\");\n return 0; //返回失败\n }\n \n SL->ListData[++SL->ListLen] = data; // 把数据插入到下标为(ListLen+1)的位置\n \n return 1;//返回成功\n }\n \n /* 插入元素到顺序表指定位置 */\n int SeqListInsert(SeqListType *SL, int n, DATA data){\n int i;\n \n if(SL->ListLen >= MAXSIZE){ // 顺序表已满\n printf(\"顺序表已经满了 不能再插入\\n\");\n return 0;\n }\n \n if(n < 1 || n > SL->ListLen){\n printf(\"要插入的位置错误\\n\");\n return 0;\n }\n \n for(i = SL->ListLen; i>=n; i--){ //移动要插入数据的后面的数据\n SL->ListData[i+1] = SL->ListData[i];\n }\n SL->ListData[n] = data; //插入数据\n SL->ListLen++; //数据个数加一\n \n return 1;\n }\n \n int SeqListDelete(SeqListType *SL, int n){\n int i;\n if(n < 1 || n > SL->ListLen+1){\n printf(\"结点错误 不能删除\\n\");\n return 0;\n }\n \n for(i=n; iListLen; i++){ //移动要删除数据的后面的数据\n SL->ListData[i] = SL->ListData[i + 1];\n }\n SL->ListLen--;\n \n return 1;\n }\n \n DATA *SeqListFindByNum(SeqListType *SL, int n){\n if(n < 1 || n > SL->ListLen+1){\n printf(\"序号错误 获取失败\");\n return NULL;\n }\n \n return &(SL->ListData[n]); // 返回指针增加通用性\n }\n \n int SeqListFindByCont(SeqListType *SL, char *key){\n \n int i;\n for(i = 0; i <= SL->ListLen; i++){\n if(strcmp(SL->ListData[i].key, key) == 0){\n return i;\n }\n }\n \n return 0; //遍历没有找到\n }\n\n\n> #### main.c\n\n /* 测试文件:调用测试函数*/\n #include \n #include \"SeqList.h\"\n \n /*遍历顺序表中结点*/\n int SeqListAll(SeqListType *SL){\n int i;\n for(i = 0; i <= SL->ListLen; i++){\n //输出中第一个是0, 即下标为0的位置的存储的数据\n printf(\"(%s %s %d) \\n\", SL->ListData[i].key, SL->ListData[i].name, SL->ListData[i].age);\n }\n \n return 0;\n }\n \n int main(void){\n int i, k;\n SeqListType SL; //定义顺序表变量\n DATA data, *data1; //定义结点保存数据类型变量和指针变量\n char key[15]; //保存关键字\n \n SeqListInit(&SL); //初始化数据表\n \n do{\n printf(\"请输入学号 姓名 年龄: \");\n fflush(stdin); //清空输入缓冲区\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n if(data.age){ //年龄不是0 退出循环\n if(!SeqListAdd(&SL, data)){//添加元素到顺序表\n break; //当添加失败 退出循环\n }\n }else{ //当年龄为0 退出循环\n break;\n }\n }while(1);\n \n printf(\"顺序表为: \\n\");\n SeqListAll(&SL);\n \n while(1){\n fflush(stdin);\n printf(\"\\n\\n输入操作\\n1.获取结点位置元素\\t2.内容查询\\t3.添加\\t4.插入\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:\");\n scanf(\"%d\", &k);\n if(k == 8){\n break;\n }\n switch(k){\n case 1:\n printf(\"输入元素位置:\");\n scanf(\"%d\", &i);\n data1 = SeqListFindByNum(&SL, i);\n printf(\"元素为:(%s %s %d) \\n\", data1->key, data1->name, data1->age);\n break;\n case 2:\n printf(\"输入元素key(学号):\");\n scanf(\"%s\", &key);\n i = SeqListFindByCont(&SL, key);\n if(i == 0){\n printf(\"没有找到对应元素!\");\n break;\n }\n data1 = SeqListFindByNum(&SL, i);\n printf(\"位置为: %d ,元素为:(%s %s %d) \\n\", i, data1->key, data1->name, data1->age);\n break;\n case 3:\n printf(\"输入元素内容:\");\n scanf(\"%s %s %d\", &data.key, &data.name, &data.age);\n SeqListAdd(&SL, data);\n break;\n case 4:\n printf(\"输入位置和元素内容:\");\n scanf(\"%d %s %s %d\", &i, &data.key, &data.name, &data.age);\n SeqListInsert(&SL, i, data);\n break;\n case 5:\n printf(\"输入要删除的位置:\");\n scanf(\"%d\", &i);\n SeqListDelete(&SL, i);\n break;\n case 6:\n printf(\"-----%d------\\n\", SeqListLength(&SL));\n break;\n case 7:\n SeqListAll(&SL);\n break;\n }\n }\n \n return 0;\n }\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170811_xxb","published":1,"date":"2020-01-15T05:50:39.623Z","updated":"2021-03-10T13:50:15.332Z","layout":"post","photos":[],"link":"","_id":"ckm3invdu007824uj9bgrx0ga","content":"
\n

顺序表的初始化,增加,插入,删除等功能的实例

\n
\n
\n

工程一共包含4个文件

\n
    \n
  1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体
  2. \n
  3. SeqList.h :定义线性表结构体,声明全局的宏定义,函数的声明
  4. \n
  5. SeqList.c :具体的函数实现
  6. \n
  7. main.c : 测试文件
  8. \n
\n
\n
\n

参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html
参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/

\n
\n\n
\n

Entity.h

\n
typedef struct{\n    char key[15];   //结点的关键字\n    char name[20];\n    int age;\n} DATA; //定义结点类型 可定义为简单类型或者结构体\n
\n

SeqList.h

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include <string.h>\n#include "Entity.h"\n#define MAXSIZE 100 //定义线性表最大长度\n\ntypedef struct {\n    DATA ListData[MAXSIZE + 1]; //保存顺序表的数组(真正的数据从下标为1的位置开始)\n    int ListLen;                //顺序表结点个数(已存结点);默认为0,表示没有数据\n} SeqListType;\n\nvoid SeqListInit(SeqListType *SL);                      //初始化顺序表\nint SeqListLength(SeqListType *SL);                     // 返回顺序表的元素数量\nint SeqListAdd(SeqListType *SL, DATA data);             // 向顺序表中添加元素\nint SeqListInsert(SeqListType *SL, int n, DATA data);   // 向顺序表中插入元素\nint SeqListDelete(SeqListType *SL, int n);              // 删除顺序表中的数据\nDATA *SeqListFindByNum(SeqListType *SL, int n);         // 根据序号返回元素\nint SeqListFindByCont(SeqListType *SL, char *key);      // 按关键字查找\nint SeqListAll(SeqListType *SL);                        // 遍历顺序表\n
\n

SeqList.c

\n
/*函数文件:具体的函数实现代码*/\n\n#include "SeqList.h"\n\n/* 初始化顺序表 */\nvoid SeqListInit(SeqListType *SL){\n    SL->ListLen = 0;\n}\n\n/* 返回顺序表元素数量 */\nint SeqListLength(SeqListType *SL){\n    return (SL->ListLen);\n}\n\n/* 添加元素到顺序表尾 */\nint SeqListAdd(SeqListType *SL, DATA data){\n    if(SL->ListLen  >= MAXSIZE){ // 顺序表已满\n        printf("顺序表已经满了 不能再添加");\n        return 0; //返回失败\n    }\n\n    SL->ListData[++SL->ListLen] = data; // 把数据插入到下标为(ListLen+1)的位置\n\n    return 1;//返回成功\n}\n\n/* 插入元素到顺序表指定位置 */\nint SeqListInsert(SeqListType *SL, int n, DATA data){\n    int i;\n\n    if(SL->ListLen  >= MAXSIZE){ // 顺序表已满\n        printf("顺序表已经满了 不能再插入\\n");\n        return 0;\n    }\n\n    if(n < 1 || n > SL->ListLen){\n        printf("要插入的位置错误\\n");\n        return 0;\n    }\n\n    for(i = SL->ListLen; i>=n; i--){ //移动要插入数据的后面的数据\n        SL->ListData[i+1] = SL->ListData[i];\n    }\n    SL->ListData[n] = data;     //插入数据\n    SL->ListLen++;              //数据个数加一\n\n    return 1;\n}\n\nint SeqListDelete(SeqListType *SL, int n){\n    int i;\n    if(n < 1 || n > SL->ListLen+1){\n        printf("结点错误 不能删除\\n");\n        return 0;\n    }\n\n    for(i=n; i<SL->ListLen; i++){   //移动要删除数据的后面的数据\n        SL->ListData[i] = SL->ListData[i + 1];\n    }\n    SL->ListLen--;\n\n    return 1;\n}\n\nDATA *SeqListFindByNum(SeqListType *SL, int n){\n    if(n < 1 || n > SL->ListLen+1){\n        printf("序号错误 获取失败");\n        return NULL;\n    }\n\n    return &(SL->ListData[n]); // 返回指针增加通用性\n}\n\nint SeqListFindByCont(SeqListType *SL, char *key){\n\n    int i;\n    for(i = 0; i <= SL->ListLen; i++){\n        if(strcmp(SL->ListData[i].key, key) == 0){\n            return i;\n        }\n    }\n\n    return 0; //遍历没有找到\n}\n
\n

main.c

\n
/* 测试文件:调用测试函数*/\n#include <stdio.h>\n#include "SeqList.h"\n\n/*遍历顺序表中结点*/\nint SeqListAll(SeqListType *SL){\n    int i;\n    for(i = 0; i <= SL->ListLen; i++){\n        //输出中第一个是0, 即下标为0的位置的存储的数据\n        printf("(%s %s %d) \\n", SL->ListData[i].key, SL->ListData[i].name, SL->ListData[i].age);\n    }\n\n    return 0;\n}\n\nint main(void){\n    int i, k;\n    SeqListType SL;         //定义顺序表变量\n    DATA data, *data1;      //定义结点保存数据类型变量和指针变量\n    char key[15];           //保存关键字\n\n    SeqListInit(&SL);       //初始化数据表\n\n    do{\n        printf("请输入学号 姓名 年龄: ");\n        fflush(stdin);      //清空输入缓冲区\n        scanf("%s %s %d", &data.key, &data.name, &data.age);\n        if(data.age){       //年龄不是0 退出循环\n            if(!SeqListAdd(&SL, data)){//添加元素到顺序表\n                break;      //当添加失败 退出循环\n            }\n        }else{              //当年龄为0 退出循环\n            break;\n        }\n    }while(1);\n\n    printf("顺序表为: \\n");\n    SeqListAll(&SL);\n\n    while(1){\n        fflush(stdin);\n        printf("\\n\\n输入操作\\n1.获取结点位置元素\\t2.内容查询\\t3.添加\\t4.插入\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:");\n        scanf("%d", &k);\n        if(k == 8){\n            break;\n        }\n        switch(k){\n            case 1:\n                printf("输入元素位置:");\n                scanf("%d", &i);\n                data1 = SeqListFindByNum(&SL, i);\n                printf("元素为:(%s %s %d) \\n", data1->key, data1->name, data1->age);\n                break;\n            case 2:\n                printf("输入元素key(学号):");\n                scanf("%s", &key);\n                i = SeqListFindByCont(&SL, key);\n                if(i == 0){\n                    printf("没有找到对应元素!");\n                    break;\n                }\n                data1 = SeqListFindByNum(&SL, i);\n                printf("位置为: %d ,元素为:(%s %s %d) \\n", i, data1->key, data1->name, data1->age);\n                break;\n            case 3:\n                printf("输入元素内容:");\n                scanf("%s %s %d", &data.key, &data.name, &data.age);\n                SeqListAdd(&SL, data);\n                break;\n            case 4:\n                printf("输入位置和元素内容:");\n                scanf("%d %s %s %d", &i, &data.key, &data.name, &data.age);\n                SeqListInsert(&SL, i, data);\n                break;\n            case 5:\n                printf("输入要删除的位置:");\n                scanf("%d", &i);\n                SeqListDelete(&SL, i);\n                break;\n            case 6:\n                printf("-----%d------\\n", SeqListLength(&SL));\n                break;\n            case 7:\n                SeqListAll(&SL);\n                 break;\n        }\n    }\n\n    return 0;\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

顺序表的初始化,增加,插入,删除等功能的实例

\n
\n
\n

工程一共包含4个文件

\n
    \n
  1. Entity.h :声明线性表的元素的类型。可以是基本数据类型也可以是结构体
  2. \n
  3. SeqList.h :定义线性表结构体,声明全局的宏定义,函数的声明
  4. \n
  5. SeqList.c :具体的函数实现
  6. \n
  7. main.c : 测试文件
  8. \n
\n
\n
\n

参考博客文章: http://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html
参考博客文章: http://blog.163.com/jiaoruijun07@126/blog/static/68943278201042064246409/

\n
","more":"
\n

Entity.h

\n
typedef struct{\n    char key[15];   //结点的关键字\n    char name[20];\n    int age;\n} DATA; //定义结点类型 可定义为简单类型或者结构体\n
\n

SeqList.h

\n
/*\n    头文件:数据结构的定义和操作原型\n\n*/\n#include <stdio.h>\n#include <string.h>\n#include "Entity.h"\n#define MAXSIZE 100 //定义线性表最大长度\n\ntypedef struct {\n    DATA ListData[MAXSIZE + 1]; //保存顺序表的数组(真正的数据从下标为1的位置开始)\n    int ListLen;                //顺序表结点个数(已存结点);默认为0,表示没有数据\n} SeqListType;\n\nvoid SeqListInit(SeqListType *SL);                      //初始化顺序表\nint SeqListLength(SeqListType *SL);                     // 返回顺序表的元素数量\nint SeqListAdd(SeqListType *SL, DATA data);             // 向顺序表中添加元素\nint SeqListInsert(SeqListType *SL, int n, DATA data);   // 向顺序表中插入元素\nint SeqListDelete(SeqListType *SL, int n);              // 删除顺序表中的数据\nDATA *SeqListFindByNum(SeqListType *SL, int n);         // 根据序号返回元素\nint SeqListFindByCont(SeqListType *SL, char *key);      // 按关键字查找\nint SeqListAll(SeqListType *SL);                        // 遍历顺序表\n
\n

SeqList.c

\n
/*函数文件:具体的函数实现代码*/\n\n#include "SeqList.h"\n\n/* 初始化顺序表 */\nvoid SeqListInit(SeqListType *SL){\n    SL->ListLen = 0;\n}\n\n/* 返回顺序表元素数量 */\nint SeqListLength(SeqListType *SL){\n    return (SL->ListLen);\n}\n\n/* 添加元素到顺序表尾 */\nint SeqListAdd(SeqListType *SL, DATA data){\n    if(SL->ListLen  >= MAXSIZE){ // 顺序表已满\n        printf("顺序表已经满了 不能再添加");\n        return 0; //返回失败\n    }\n\n    SL->ListData[++SL->ListLen] = data; // 把数据插入到下标为(ListLen+1)的位置\n\n    return 1;//返回成功\n}\n\n/* 插入元素到顺序表指定位置 */\nint SeqListInsert(SeqListType *SL, int n, DATA data){\n    int i;\n\n    if(SL->ListLen  >= MAXSIZE){ // 顺序表已满\n        printf("顺序表已经满了 不能再插入\\n");\n        return 0;\n    }\n\n    if(n < 1 || n > SL->ListLen){\n        printf("要插入的位置错误\\n");\n        return 0;\n    }\n\n    for(i = SL->ListLen; i>=n; i--){ //移动要插入数据的后面的数据\n        SL->ListData[i+1] = SL->ListData[i];\n    }\n    SL->ListData[n] = data;     //插入数据\n    SL->ListLen++;              //数据个数加一\n\n    return 1;\n}\n\nint SeqListDelete(SeqListType *SL, int n){\n    int i;\n    if(n < 1 || n > SL->ListLen+1){\n        printf("结点错误 不能删除\\n");\n        return 0;\n    }\n\n    for(i=n; i<SL->ListLen; i++){   //移动要删除数据的后面的数据\n        SL->ListData[i] = SL->ListData[i + 1];\n    }\n    SL->ListLen--;\n\n    return 1;\n}\n\nDATA *SeqListFindByNum(SeqListType *SL, int n){\n    if(n < 1 || n > SL->ListLen+1){\n        printf("序号错误 获取失败");\n        return NULL;\n    }\n\n    return &(SL->ListData[n]); // 返回指针增加通用性\n}\n\nint SeqListFindByCont(SeqListType *SL, char *key){\n\n    int i;\n    for(i = 0; i <= SL->ListLen; i++){\n        if(strcmp(SL->ListData[i].key, key) == 0){\n            return i;\n        }\n    }\n\n    return 0; //遍历没有找到\n}\n
\n

main.c

\n
/* 测试文件:调用测试函数*/\n#include <stdio.h>\n#include "SeqList.h"\n\n/*遍历顺序表中结点*/\nint SeqListAll(SeqListType *SL){\n    int i;\n    for(i = 0; i <= SL->ListLen; i++){\n        //输出中第一个是0, 即下标为0的位置的存储的数据\n        printf("(%s %s %d) \\n", SL->ListData[i].key, SL->ListData[i].name, SL->ListData[i].age);\n    }\n\n    return 0;\n}\n\nint main(void){\n    int i, k;\n    SeqListType SL;         //定义顺序表变量\n    DATA data, *data1;      //定义结点保存数据类型变量和指针变量\n    char key[15];           //保存关键字\n\n    SeqListInit(&SL);       //初始化数据表\n\n    do{\n        printf("请输入学号 姓名 年龄: ");\n        fflush(stdin);      //清空输入缓冲区\n        scanf("%s %s %d", &data.key, &data.name, &data.age);\n        if(data.age){       //年龄不是0 退出循环\n            if(!SeqListAdd(&SL, data)){//添加元素到顺序表\n                break;      //当添加失败 退出循环\n            }\n        }else{              //当年龄为0 退出循环\n            break;\n        }\n    }while(1);\n\n    printf("顺序表为: \\n");\n    SeqListAll(&SL);\n\n    while(1){\n        fflush(stdin);\n        printf("\\n\\n输入操作\\n1.获取结点位置元素\\t2.内容查询\\t3.添加\\t4.插入\\t5.删除\\t6.求长度\\t7.遍历\\t8.退出\\n:");\n        scanf("%d", &k);\n        if(k == 8){\n            break;\n        }\n        switch(k){\n            case 1:\n                printf("输入元素位置:");\n                scanf("%d", &i);\n                data1 = SeqListFindByNum(&SL, i);\n                printf("元素为:(%s %s %d) \\n", data1->key, data1->name, data1->age);\n                break;\n            case 2:\n                printf("输入元素key(学号):");\n                scanf("%s", &key);\n                i = SeqListFindByCont(&SL, key);\n                if(i == 0){\n                    printf("没有找到对应元素!");\n                    break;\n                }\n                data1 = SeqListFindByNum(&SL, i);\n                printf("位置为: %d ,元素为:(%s %s %d) \\n", i, data1->key, data1->name, data1->age);\n                break;\n            case 3:\n                printf("输入元素内容:");\n                scanf("%s %s %d", &data.key, &data.name, &data.age);\n                SeqListAdd(&SL, data);\n                break;\n            case 4:\n                printf("输入位置和元素内容:");\n                scanf("%d %s %s %d", &i, &data.key, &data.name, &data.age);\n                SeqListInsert(&SL, i, data);\n                break;\n            case 5:\n                printf("输入要删除的位置:");\n                scanf("%d", &i);\n                SeqListDelete(&SL, i);\n                break;\n            case 6:\n                printf("-----%d------\\n", SeqListLength(&SL));\n                break;\n            case 7:\n                SeqListAll(&SL);\n                 break;\n        }\n    }\n\n    return 0;\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"普通二叉树","comments":1,"description":null,"_content":" \n\n> 二叉树:\n\n 二叉树性质:\n 1.在二叉树的第i层的结点总数最多有2^(i-1)个结点\n 2.深度为k的二叉树最多有2^(k) - 1个结点,最少有k个结点\n 3.二叉树,如果其叶结点为n0,而度为2的结点总数为n2,则n0=n2+1\n 4.有n个结点的完全二叉树的深度k为:k=[log2(n)]+1\n 5.有n个结点的完全二叉树各结点如果用顺序表存储,对任意结点i,有如下关系:\n 如果 i != 1,则其父节点的编号为i/2\n 如果 2*i <= n,则其左子树根节点的编号为2*i;若 2*i>n,则无左子树\n 如果 2*i+1 <= n,则右子树根节点的编号为2*i+1;若 2*i+1>n,则无右子树\n \n\n> 二叉树的存储:\n\n\n\n 1.顺序存储结构:若是完全二叉树,则某个结点的父节点=(该节点的位置/2),其子节点为(该节点的位置*2)和(该节点的位置*2+1),从1开始计算;\n 若不是完全二叉树,可以在没有结点数据的位置置为空,模拟成完全二叉树(然后同上);\n \n{% qnimg tree001.png title:完全二叉树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 如图二叉树,如果使用顺序存储结则数据结构定义为如下sqTree数组:\n #definde MAXSIZE 100\n typedef int DATA;\n typedef DATA SeqBinTree[MAXSIZE];\n SeqBinTree sqTree; //顺序存储结构\n 对于上面的完全二叉树,最终使用顺序存储结构后,得到的效果为如下图:\n\n\n{% qnimg tree002.png title:顺序存储实现完全二叉树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 通过图中规律可发现:\n 1.求某个结点的子结点,只需把该结点在数组中的位置(非下标,而是下标+1)乘2,则子节点为所得的位置的结点以及其下一个结点;\n 2.求某个结点的父结点,只需把该结点在数组中的位置除以2,说的的商即为父结点位置;\n 3.对于非完全二叉树,可以模拟为完全二叉树使用,即把没有结点的位置“空出来”,表示没有结点,如图,假设I节点不存在,则9的位置即为空\n \n 顺序存储结构问题:占用内存连续且必须提前分配足够的内存,不能扩容,不够灵活,所以一般使用链式存储结构存储树;\n\n\n{% qnimg tree003.png title:链式存储结构 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 2.二叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针(没有则赋值为空);\n \n 对于链式存储结构,可以使用树的基本原理,即树相当于多个子树嵌套,树中包含多个子树,而子树也是一个树结构,所以算法中经常涉及到递归调用,递归查询;\n 其中left指针存放左子树的根节点的地址,right指针存放右子树的根节点的地址;\n \n\n 2.三叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针以及一个指向父结点的指针\n \n\n\n> 二叉树的遍历:\n\n 1.先序遍历(DLR): 顺序:根-左-右\n 2.中序遍历(LDR): 顺序:左-根-右\n 3.后序遍历(LRD)\" 顺序:左-右-跟\n 4.按层遍历\n\n### 二叉顺序存储结构\n\n\n\n### 二叉链式存储结构\n \n> #### BinTree.h\n\n```c\n #include \n \n #define QUEUE_MAXSIZE 50\n \n typedef char DATA; //定义树结点的元素类型\n \n typedef struct ChainTree{ //定义二叉树结点类型\n DATA data; //结点数据\n struct ChainTree *left; //左子树结点指针\n struct ChainTree *right;//右子树结点指针\n }ChainBinTree;\n \n /*初始化二叉树根节点*/\n ChainBinTree *binTreeInit(ChainBinTree *node);\n \n /*添加数据到二叉树*/\n int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n);\n \n /*返回左子节点和右子节点*/\n ChainBinTree *binTreeLeft(ChainBinTree *bt);\n \n ChainBinTree *binTreeRight(ChainBinTree *bt);\n \n /*判断二叉树是否为空*/\n int binTreeIsEmpty(ChainBinTree *bt);\n \n /*求二叉树深度*/\n int binTreeDepth(ChainBinTree *bt);\n \n /*寻找值为data的结点*/\n ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data);\n \n /*清空树*/\n void binTreeClear(ChainBinTree *bt);\n \n /********************************树的遍历***************************/\n \n /*遍历树要进行的操作*/\n void oper(ChainBinTree *p);\n \n \n /*先序遍历*/\n void binTree_DLR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n \n /*中序遍历*/\n void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n \n /*后序遍历*/\n void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n \n /*按层遍历*/\n void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n```\n\n> #### BinTree.c\n\n```c\n #include \"BinTree.h\"\n \n /*初始化二叉树根节点*/\n ChainBinTree *binTreeInit(ChainBinTree *node){\n if(node != NULL)\n return node;\n else\n return NULL;\n }\n \n /*添加数据到二叉树 bt为要添加的位置的父节点 node为添加的节点 n=1表示左子树 n=2表示右子树*/\n int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n){\n if(bt == NULL){\n printf(\"父节点不存在!\\n\");\n return 0;\n }\n \n switch(n){\n //添加到左子树\n case 1:\n if(bt->left){\n printf(\"左子树不为空, 不能添加!\\n\");\n return 0;\n }else{\n bt->left = node;\n }\n break;\n case 2:\n if(bt->right){\n printf(\"右子树不为空, 不能添加!\\n\");\n return 0;\n }else{\n bt->right = node;\n }\n break;\n default:\n printf(\"参数错误\");\n return 0;\n }\n \n return 1;\n }\n \n /*返回左子节点和右子节点*/\n ChainBinTree *binTreeLeft(ChainBinTree *bt){\n if(bt)\n return bt->left;\n else\n return NULL;\n }\n ChainBinTree *binTreeRight(ChainBinTree *bt){\n if(bt)\n return bt->right;\n else\n return NULL;\n }\n \n /*判断二叉树是否为空*/\n int binTreeIsEmpty(ChainBinTree *bt){\n if(bt)\n return 0;\n else\n return 1;\n \n }\n \n /*求二叉树深度*/\n int binTreeDepth(ChainBinTree *bt){\n int dep1,dep2;\n if(bt == NULL){\n return 0; //空树\n }else{\n //递归调用 递归子树直到最后的叶子结点没有子节点,返回0\n dep1 = binTreeDepth(bt->left);\n dep2 = binTreeDepth(bt->right);\n \n //返回子树中深度更深的子树的深度\n if(dep1 > dep2){\n return dep1 + 1;\n }else{\n return dep2 + 1;\n }\n }\n }\n \n /*寻找值为data的结点*/\n ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data){\n ChainBinTree *p;\n if(bt == NULL)\n return NULL;\n else{\n if(bt->data == data){\n return bt;\n }else{\n //递归调用\n if(p = binTreeFind(bt->left, data))\n return p;\n else if(p = binTreeFind(bt->right, data))\n return p;\n else\n return NULL;\n }\n }\n }\n \n /*清空树*/\n void binTreeClear(ChainBinTree *bt){\n if(bt){\n binTreeClear(bt->left);\n binTreeClear(bt->right);\n free(bt);\n }\n \n return;\n }\n \n /********************************树的遍历***************************/\n \n /*遍历树要进行的操作*/\n void oper(ChainBinTree *p){\n printf(\"%c \\t\", p->data);\n return;\n }\n \n \n /*先序遍历*/\n void binTree_DLR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){\n if(bt){\n oper(bt);\n binTree_DLR(bt->left, oper);\n binTree_DLR(bt->right, oper);\n }\n \n return;\n }\n \n /*中序遍历*/\n void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){\n if(bt){\n binTree_LDR(bt->left, oper);\n oper(bt);\n binTree_LDR(bt->right, oper);\n }\n \n return;\n }\n \n /*后序遍历*/\n void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){\n if(bt){\n binTree_LRD(bt->left, oper);\n binTree_LRD(bt->right, oper);\n oper(bt);\n }\n \n return;\n }\n \n /*按层遍历*/\n void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){\n ChainBinTree *p;\n ChainBinTree *q[QUEUE_MAXSIZE]; //定义一个顺序队列,先进先出\n int head = 0, tail = 0; //队首队尾序号\n if(bt){ //如果队首指针不为空\n tail = (tail + 1) % QUEUE_MAXSIZE; //计算循环队列队尾序号\n q[tail] = bt; //把二叉树根指针进队\n }\n \n //本质上:每次tail进行两次改变(只有当是完全二叉树,其他情况则有不同),然后head进行一次改变,实现按层把树的元素放到队列中去\n //即每次把head指向的结点的两个子节点存放到队列,并且把该结点进行操作(执行oper方法);\n //当tail不再增加,也就是说再也没有哪个未读取得结点还拥有子节点了,则剩下的就是把队列中剩余未操作的结点进行操作,直到队列为空,即head==tail,退出循环\n while(head != tail){ //队列不为空, 进行循环\n head = (head + 1) % QUEUE_MAXSIZE; //计算循环队列的队首序号\n p = q[head]; //获取队列元素\n oper(p); //处理队首元素\n \n if(p->left != NULL){ //若结点存在左子树, 则左子树指针进队\n tail = (tail + 1) % QUEUE_MAXSIZE;\n q[tail] = p->left;\n }\n \n if(p->right != NULL){ //若结点存在右子树, 则右子树指针进队\n tail = (tail + 1) % QUEUE_MAXSIZE;\n q[tail] = p->right;\n }\n }\n \n return;\n }\n```\n\n{% qnimg IMG_0796.JPG title:按层遍历 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n按层遍历过程。。。\n\n> #### main.c\n\n```c\n #include \n #include \"BinTree.h\"\n \n ChainBinTree *initRoot(){\n ChainBinTree *node;\n \n if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){\n printf(\"\\n输入根数据结点\");\n scanf(\"%s\", &node->data);\n node->left = NULL;\n node->right = NULL;\n \n return binTreeInit(node);\n }\n \n return NULL;\n }\n \n void addNode(ChainBinTree *bt){\n ChainBinTree *node, *parent;//存放新增的结点和要挂接的父节点\n DATA data;\n char select;\n if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){//分配内存\n printf(\"\\n输入二叉树结点数据:\");\n fflush(stdin);\n scanf(\"%s\", &node->data);\n node->left = NULL;\n node->right = NULL;\n \n printf(\"输入父结点数据:\");\n fflush(stdin);\n scanf(\"%s\",&data);\n parent = binTreeFind(bt, data);//查找结点\n if(!parent){\n printf(\"未找到结点\\n\");\n free(node);\n return;\n }\n \n printf(\"1.添加到左子树\\n2.添加到右子树\");\n do{\n select = getch();\n select -= '0';\n if(select == 1 || select == 2){\n binTreeAddNode(parent, node, select);//添加结点到二叉树\n }\n }while(select != 1 && select != 2);\n \n return;\n \n }\n }\n \n int main()\n {\n ChainBinTree *root = NULL; //root为指向二叉树根节点的指针\n char select;\n void (*oper1)(); //指向函数的指针\n oper1 = oper; //指向具体操作的函数\n do{\n printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 0.退出\");\n select = getch();\n switch(select){\n case '1':\n root = initRoot();\n break;\n case '2':\n addNode(root);\n break;\n case '3':\n binTree_DLR(root, oper1);\n printf(\"\\n\");\n break;\n case '4':\n binTree_LDR(root, oper1);\n printf(\"\\n\");\n break;\n case '5':\n binTree_LRD(root, oper1);\n printf(\"\\n\");\n break;\n case '6':\n binTree_Level(root, oper1);\n printf(\"\\n\");\n break;\n case '7':\n printf(\"%d\", binTreeDepth(root));\n break;\n }\n }while(select != '0');\n \n binTreeClear(root);\n root = NULL;\n \n return 0;\n }\n```\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170826_ecs.md","raw":"---\ntitle: 普通二叉树\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n \n\n> 二叉树:\n\n 二叉树性质:\n 1.在二叉树的第i层的结点总数最多有2^(i-1)个结点\n 2.深度为k的二叉树最多有2^(k) - 1个结点,最少有k个结点\n 3.二叉树,如果其叶结点为n0,而度为2的结点总数为n2,则n0=n2+1\n 4.有n个结点的完全二叉树的深度k为:k=[log2(n)]+1\n 5.有n个结点的完全二叉树各结点如果用顺序表存储,对任意结点i,有如下关系:\n 如果 i != 1,则其父节点的编号为i/2\n 如果 2*i <= n,则其左子树根节点的编号为2*i;若 2*i>n,则无左子树\n 如果 2*i+1 <= n,则右子树根节点的编号为2*i+1;若 2*i+1>n,则无右子树\n \n\n> 二叉树的存储:\n\n\n\n 1.顺序存储结构:若是完全二叉树,则某个结点的父节点=(该节点的位置/2),其子节点为(该节点的位置*2)和(该节点的位置*2+1),从1开始计算;\n 若不是完全二叉树,可以在没有结点数据的位置置为空,模拟成完全二叉树(然后同上);\n \n{% qnimg tree001.png title:完全二叉树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 如图二叉树,如果使用顺序存储结则数据结构定义为如下sqTree数组:\n #definde MAXSIZE 100\n typedef int DATA;\n typedef DATA SeqBinTree[MAXSIZE];\n SeqBinTree sqTree; //顺序存储结构\n 对于上面的完全二叉树,最终使用顺序存储结构后,得到的效果为如下图:\n\n\n{% qnimg tree002.png title:顺序存储实现完全二叉树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 通过图中规律可发现:\n 1.求某个结点的子结点,只需把该结点在数组中的位置(非下标,而是下标+1)乘2,则子节点为所得的位置的结点以及其下一个结点;\n 2.求某个结点的父结点,只需把该结点在数组中的位置除以2,说的的商即为父结点位置;\n 3.对于非完全二叉树,可以模拟为完全二叉树使用,即把没有结点的位置“空出来”,表示没有结点,如图,假设I节点不存在,则9的位置即为空\n \n 顺序存储结构问题:占用内存连续且必须提前分配足够的内存,不能扩容,不够灵活,所以一般使用链式存储结构存储树;\n\n\n{% qnimg tree003.png title:链式存储结构 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 2.二叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针(没有则赋值为空);\n \n 对于链式存储结构,可以使用树的基本原理,即树相当于多个子树嵌套,树中包含多个子树,而子树也是一个树结构,所以算法中经常涉及到递归调用,递归查询;\n 其中left指针存放左子树的根节点的地址,right指针存放右子树的根节点的地址;\n \n\n 2.三叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针以及一个指向父结点的指针\n \n\n\n> 二叉树的遍历:\n\n 1.先序遍历(DLR): 顺序:根-左-右\n 2.中序遍历(LDR): 顺序:左-根-右\n 3.后序遍历(LRD)\" 顺序:左-右-跟\n 4.按层遍历\n\n### 二叉顺序存储结构\n\n\n\n### 二叉链式存储结构\n \n> #### BinTree.h\n\n```c\n #include \n \n #define QUEUE_MAXSIZE 50\n \n typedef char DATA; //定义树结点的元素类型\n \n typedef struct ChainTree{ //定义二叉树结点类型\n DATA data; //结点数据\n struct ChainTree *left; //左子树结点指针\n struct ChainTree *right;//右子树结点指针\n }ChainBinTree;\n \n /*初始化二叉树根节点*/\n ChainBinTree *binTreeInit(ChainBinTree *node);\n \n /*添加数据到二叉树*/\n int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n);\n \n /*返回左子节点和右子节点*/\n ChainBinTree *binTreeLeft(ChainBinTree *bt);\n \n ChainBinTree *binTreeRight(ChainBinTree *bt);\n \n /*判断二叉树是否为空*/\n int binTreeIsEmpty(ChainBinTree *bt);\n \n /*求二叉树深度*/\n int binTreeDepth(ChainBinTree *bt);\n \n /*寻找值为data的结点*/\n ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data);\n \n /*清空树*/\n void binTreeClear(ChainBinTree *bt);\n \n /********************************树的遍历***************************/\n \n /*遍历树要进行的操作*/\n void oper(ChainBinTree *p);\n \n \n /*先序遍历*/\n void binTree_DLR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n \n /*中序遍历*/\n void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n \n /*后序遍历*/\n void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n \n /*按层遍历*/\n void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p));\n```\n\n> #### BinTree.c\n\n```c\n #include \"BinTree.h\"\n \n /*初始化二叉树根节点*/\n ChainBinTree *binTreeInit(ChainBinTree *node){\n if(node != NULL)\n return node;\n else\n return NULL;\n }\n \n /*添加数据到二叉树 bt为要添加的位置的父节点 node为添加的节点 n=1表示左子树 n=2表示右子树*/\n int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n){\n if(bt == NULL){\n printf(\"父节点不存在!\\n\");\n return 0;\n }\n \n switch(n){\n //添加到左子树\n case 1:\n if(bt->left){\n printf(\"左子树不为空, 不能添加!\\n\");\n return 0;\n }else{\n bt->left = node;\n }\n break;\n case 2:\n if(bt->right){\n printf(\"右子树不为空, 不能添加!\\n\");\n return 0;\n }else{\n bt->right = node;\n }\n break;\n default:\n printf(\"参数错误\");\n return 0;\n }\n \n return 1;\n }\n \n /*返回左子节点和右子节点*/\n ChainBinTree *binTreeLeft(ChainBinTree *bt){\n if(bt)\n return bt->left;\n else\n return NULL;\n }\n ChainBinTree *binTreeRight(ChainBinTree *bt){\n if(bt)\n return bt->right;\n else\n return NULL;\n }\n \n /*判断二叉树是否为空*/\n int binTreeIsEmpty(ChainBinTree *bt){\n if(bt)\n return 0;\n else\n return 1;\n \n }\n \n /*求二叉树深度*/\n int binTreeDepth(ChainBinTree *bt){\n int dep1,dep2;\n if(bt == NULL){\n return 0; //空树\n }else{\n //递归调用 递归子树直到最后的叶子结点没有子节点,返回0\n dep1 = binTreeDepth(bt->left);\n dep2 = binTreeDepth(bt->right);\n \n //返回子树中深度更深的子树的深度\n if(dep1 > dep2){\n return dep1 + 1;\n }else{\n return dep2 + 1;\n }\n }\n }\n \n /*寻找值为data的结点*/\n ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data){\n ChainBinTree *p;\n if(bt == NULL)\n return NULL;\n else{\n if(bt->data == data){\n return bt;\n }else{\n //递归调用\n if(p = binTreeFind(bt->left, data))\n return p;\n else if(p = binTreeFind(bt->right, data))\n return p;\n else\n return NULL;\n }\n }\n }\n \n /*清空树*/\n void binTreeClear(ChainBinTree *bt){\n if(bt){\n binTreeClear(bt->left);\n binTreeClear(bt->right);\n free(bt);\n }\n \n return;\n }\n \n /********************************树的遍历***************************/\n \n /*遍历树要进行的操作*/\n void oper(ChainBinTree *p){\n printf(\"%c \\t\", p->data);\n return;\n }\n \n \n /*先序遍历*/\n void binTree_DLR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){\n if(bt){\n oper(bt);\n binTree_DLR(bt->left, oper);\n binTree_DLR(bt->right, oper);\n }\n \n return;\n }\n \n /*中序遍历*/\n void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){\n if(bt){\n binTree_LDR(bt->left, oper);\n oper(bt);\n binTree_LDR(bt->right, oper);\n }\n \n return;\n }\n \n /*后序遍历*/\n void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){\n if(bt){\n binTree_LRD(bt->left, oper);\n binTree_LRD(bt->right, oper);\n oper(bt);\n }\n \n return;\n }\n \n /*按层遍历*/\n void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){\n ChainBinTree *p;\n ChainBinTree *q[QUEUE_MAXSIZE]; //定义一个顺序队列,先进先出\n int head = 0, tail = 0; //队首队尾序号\n if(bt){ //如果队首指针不为空\n tail = (tail + 1) % QUEUE_MAXSIZE; //计算循环队列队尾序号\n q[tail] = bt; //把二叉树根指针进队\n }\n \n //本质上:每次tail进行两次改变(只有当是完全二叉树,其他情况则有不同),然后head进行一次改变,实现按层把树的元素放到队列中去\n //即每次把head指向的结点的两个子节点存放到队列,并且把该结点进行操作(执行oper方法);\n //当tail不再增加,也就是说再也没有哪个未读取得结点还拥有子节点了,则剩下的就是把队列中剩余未操作的结点进行操作,直到队列为空,即head==tail,退出循环\n while(head != tail){ //队列不为空, 进行循环\n head = (head + 1) % QUEUE_MAXSIZE; //计算循环队列的队首序号\n p = q[head]; //获取队列元素\n oper(p); //处理队首元素\n \n if(p->left != NULL){ //若结点存在左子树, 则左子树指针进队\n tail = (tail + 1) % QUEUE_MAXSIZE;\n q[tail] = p->left;\n }\n \n if(p->right != NULL){ //若结点存在右子树, 则右子树指针进队\n tail = (tail + 1) % QUEUE_MAXSIZE;\n q[tail] = p->right;\n }\n }\n \n return;\n }\n```\n\n{% qnimg IMG_0796.JPG title:按层遍历 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n按层遍历过程。。。\n\n> #### main.c\n\n```c\n #include \n #include \"BinTree.h\"\n \n ChainBinTree *initRoot(){\n ChainBinTree *node;\n \n if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){\n printf(\"\\n输入根数据结点\");\n scanf(\"%s\", &node->data);\n node->left = NULL;\n node->right = NULL;\n \n return binTreeInit(node);\n }\n \n return NULL;\n }\n \n void addNode(ChainBinTree *bt){\n ChainBinTree *node, *parent;//存放新增的结点和要挂接的父节点\n DATA data;\n char select;\n if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){//分配内存\n printf(\"\\n输入二叉树结点数据:\");\n fflush(stdin);\n scanf(\"%s\", &node->data);\n node->left = NULL;\n node->right = NULL;\n \n printf(\"输入父结点数据:\");\n fflush(stdin);\n scanf(\"%s\",&data);\n parent = binTreeFind(bt, data);//查找结点\n if(!parent){\n printf(\"未找到结点\\n\");\n free(node);\n return;\n }\n \n printf(\"1.添加到左子树\\n2.添加到右子树\");\n do{\n select = getch();\n select -= '0';\n if(select == 1 || select == 2){\n binTreeAddNode(parent, node, select);//添加结点到二叉树\n }\n }while(select != 1 && select != 2);\n \n return;\n \n }\n }\n \n int main()\n {\n ChainBinTree *root = NULL; //root为指向二叉树根节点的指针\n char select;\n void (*oper1)(); //指向函数的指针\n oper1 = oper; //指向具体操作的函数\n do{\n printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 0.退出\");\n select = getch();\n switch(select){\n case '1':\n root = initRoot();\n break;\n case '2':\n addNode(root);\n break;\n case '3':\n binTree_DLR(root, oper1);\n printf(\"\\n\");\n break;\n case '4':\n binTree_LDR(root, oper1);\n printf(\"\\n\");\n break;\n case '5':\n binTree_LRD(root, oper1);\n printf(\"\\n\");\n break;\n case '6':\n binTree_Level(root, oper1);\n printf(\"\\n\");\n break;\n case '7':\n printf(\"%d\", binTreeDepth(root));\n break;\n }\n }while(select != '0');\n \n binTreeClear(root);\n root = NULL;\n \n return 0;\n }\n```\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170826_ecs","published":1,"date":"2020-01-15T05:50:39.629Z","updated":"2021-03-10T13:50:15.355Z","layout":"post","photos":[],"link":"","_id":"ckm3invdv007a24uj4eh5rkaz","content":"
\n

二叉树:

\n
\n
二叉树性质:\n    1.在二叉树的第i层的结点总数最多有2^(i-1)个结点\n    2.深度为k的二叉树最多有2^(k) - 1个结点,最少有k个结点\n    3.二叉树,如果其叶结点为n0,而度为2的结点总数为n2,则n0=n2+1\n    4.有n个结点的完全二叉树的深度k为:k=[log2(n)]+1\n    5.有n个结点的完全二叉树各结点如果用顺序表存储,对任意结点i,有如下关系:\n        如果 i != 1,则其父节点的编号为i/2\n        如果 2*i <= n,则其左子树根节点的编号为2*i;若 2*i>n,则无左子树\n        如果 2*i+1 <= n,则右子树根节点的编号为2*i+1;若 2*i+1>n,则无右子树\n
\n

二叉树的存储:

\n
\n\n
1.顺序存储结构:若是完全二叉树,则某个结点的父节点=(该节点的位置/2),其子节点为(该节点的位置*2)和(该节点的位置*2+1),从1开始计算;\n                若不是完全二叉树,可以在没有结点数据的位置置为空,模拟成完全二叉树(然后同上);\n
\"图片说明\"\n
如图二叉树,如果使用顺序存储结则数据结构定义为如下sqTree数组:\n    #definde MAXSIZE 100\n    typedef int DATA;\n    typedef DATA SeqBinTree[MAXSIZE];\n    SeqBinTree sqTree;       //顺序存储结构\n对于上面的完全二叉树,最终使用顺序存储结构后,得到的效果为如下图:\n
\"图片说明\"\n
通过图中规律可发现:\n    1.求某个结点的子结点,只需把该结点在数组中的位置(非下标,而是下标+1)乘2,则子节点为所得的位置的结点以及其下一个结点;\n    2.求某个结点的父结点,只需把该结点在数组中的位置除以2,说的的商即为父结点位置;\n    3.对于非完全二叉树,可以模拟为完全二叉树使用,即把没有结点的位置“空出来”,表示没有结点,如图,假设I节点不存在,则9的位置即为空\n\n顺序存储结构问题:占用内存连续且必须提前分配足够的内存,不能扩容,不够灵活,所以一般使用链式存储结构存储树;\n
\"图片说明\"\n
2.二叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针(没有则赋值为空);\n\n    对于链式存储结构,可以使用树的基本原理,即树相当于多个子树嵌套,树中包含多个子树,而子树也是一个树结构,所以算法中经常涉及到递归调用,递归查询;\n    其中left指针存放左子树的根节点的地址,right指针存放右子树的根节点的地址;\n\n\n2.三叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针以及一个指向父结点的指针\n
\n

二叉树的遍历:

\n
\n
1.先序遍历(DLR): 顺序:根-左-右\n2.中序遍历(LDR): 顺序:左-根-右\n3.后序遍历(LRD)" 顺序:左-右-跟\n4.按层遍历\n

二叉顺序存储结构

二叉链式存储结构

\n

BinTree.h

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>

#define QUEUE_MAXSIZE 50

typedef char DATA; //定义树结点的元素类型

typedef struct ChainTree{ //定义二叉树结点类型
DATA data; //结点数据
struct ChainTree *left; //左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainBinTree;

/*初始化二叉树根节点*/
ChainBinTree *binTreeInit(ChainBinTree *node);

/*添加数据到二叉树*/
int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n);

/*返回左子节点和右子节点*/
ChainBinTree *binTreeLeft(ChainBinTree *bt);

ChainBinTree *binTreeRight(ChainBinTree *bt);

/*判断二叉树是否为空*/
int binTreeIsEmpty(ChainBinTree *bt);

/*求二叉树深度*/
int binTreeDepth(ChainBinTree *bt);

/*寻找值为data的结点*/
ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data);

/*清空树*/
void binTreeClear(ChainBinTree *bt);

/********************************树的遍历***************************/

/*遍历树要进行的操作*/
void oper(ChainBinTree *p);


/*先序遍历*/
void binTree_DLR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));

/*中序遍历*/
void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));

/*后序遍历*/
void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p));

/*按层遍历*/
void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p));
\n
\n

BinTree.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include \"BinTree.h\"

/*初始化二叉树根节点*/
ChainBinTree *binTreeInit(ChainBinTree *node){
if(node != NULL)
return node;
else
return NULL;
}

/*添加数据到二叉树 bt为要添加的位置的父节点 node为添加的节点 n=1表示左子树 n=2表示右子树*/
int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n){
if(bt == NULL){
printf(\"父节点不存在!\\n\");
return 0;
}

switch(n){
//添加到左子树
case 1:
if(bt->left){
printf(\"左子树不为空, 不能添加!\\n\");
return 0;
}else{
bt->left = node;
}
break;
case 2:
if(bt->right){
printf(\"右子树不为空, 不能添加!\\n\");
return 0;
}else{
bt->right = node;
}
break;
default:
printf(\"参数错误\");
return 0;
}

return 1;
}

/*返回左子节点和右子节点*/
ChainBinTree *binTreeLeft(ChainBinTree *bt){
if(bt)
return bt->left;
else
return NULL;
}
ChainBinTree *binTreeRight(ChainBinTree *bt){
if(bt)
return bt->right;
else
return NULL;
}

/*判断二叉树是否为空*/
int binTreeIsEmpty(ChainBinTree *bt){
if(bt)
return 0;
else
return 1;

}

/*求二叉树深度*/
int binTreeDepth(ChainBinTree *bt){
int dep1,dep2;
if(bt == NULL){
return 0; //空树
}else{
//递归调用 递归子树直到最后的叶子结点没有子节点,返回0
dep1 = binTreeDepth(bt->left);
dep2 = binTreeDepth(bt->right);

//返回子树中深度更深的子树的深度
if(dep1 > dep2){
return dep1 + 1;
}else{
return dep2 + 1;
}
}
}

/*寻找值为data的结点*/
ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data){
ChainBinTree *p;
if(bt == NULL)
return NULL;
else{
if(bt->data == data){
return bt;
}else{
//递归调用
if(p = binTreeFind(bt->left, data))
return p;
else if(p = binTreeFind(bt->right, data))
return p;
else
return NULL;
}
}
}

/*清空树*/
void binTreeClear(ChainBinTree *bt){
if(bt){
binTreeClear(bt->left);
binTreeClear(bt->right);
free(bt);
}

return;
}

/********************************树的遍历***************************/

/*遍历树要进行的操作*/
void oper(ChainBinTree *p){
printf(\"%c \\t\", p->data);
return;
}


/*先序遍历*/
void binTree_DLR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){
if(bt){
oper(bt);
binTree_DLR(bt->left, oper);
binTree_DLR(bt->right, oper);
}

return;
}

/*中序遍历*/
void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){
if(bt){
binTree_LDR(bt->left, oper);
oper(bt);
binTree_LDR(bt->right, oper);
}

return;
}

/*后序遍历*/
void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){
if(bt){
binTree_LRD(bt->left, oper);
binTree_LRD(bt->right, oper);
oper(bt);
}

return;
}

/*按层遍历*/
void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){
ChainBinTree *p;
ChainBinTree *q[QUEUE_MAXSIZE]; //定义一个顺序队列,先进先出
int head = 0, tail = 0; //队首队尾序号
if(bt){ //如果队首指针不为空
tail = (tail + 1) % QUEUE_MAXSIZE; //计算循环队列队尾序号
q[tail] = bt; //把二叉树根指针进队
}

//本质上:每次tail进行两次改变(只有当是完全二叉树,其他情况则有不同),然后head进行一次改变,实现按层把树的元素放到队列中去
//即每次把head指向的结点的两个子节点存放到队列,并且把该结点进行操作(执行oper方法);
//当tail不再增加,也就是说再也没有哪个未读取得结点还拥有子节点了,则剩下的就是把队列中剩余未操作的结点进行操作,直到队列为空,即head==tail,退出循环
while(head != tail){ //队列不为空, 进行循环
head = (head + 1) % QUEUE_MAXSIZE; //计算循环队列的队首序号
p = q[head]; //获取队列元素
oper(p); //处理队首元素

if(p->left != NULL){ //若结点存在左子树, 则左子树指针进队
tail = (tail + 1) % QUEUE_MAXSIZE;
q[tail] = p->left;
}

if(p->right != NULL){ //若结点存在右子树, 则右子树指针进队
tail = (tail + 1) % QUEUE_MAXSIZE;
q[tail] = p->right;
}
}

return;
}
\n\"图片说明\"\n

按层遍历过程。。。

\n
\n

main.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>
#include \"BinTree.h\"

ChainBinTree *initRoot(){
ChainBinTree *node;

if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){
printf(\"\\n输入根数据结点\");
scanf(\"%s\", &node->data);
node->left = NULL;
node->right = NULL;

return binTreeInit(node);
}

return NULL;
}

void addNode(ChainBinTree *bt){
ChainBinTree *node, *parent;//存放新增的结点和要挂接的父节点
DATA data;
char select;
if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){//分配内存
printf(\"\\n输入二叉树结点数据:\");
fflush(stdin);
scanf(\"%s\", &node->data);
node->left = NULL;
node->right = NULL;

printf(\"输入父结点数据:\");
fflush(stdin);
scanf(\"%s\",&data);
parent = binTreeFind(bt, data);//查找结点
if(!parent){
printf(\"未找到结点\\n\");
free(node);
return;
}

printf(\"1.添加到左子树\\n2.添加到右子树\");
do{
select = getch();
select -= '0';
if(select == 1 || select == 2){
binTreeAddNode(parent, node, select);//添加结点到二叉树
}
}while(select != 1 && select != 2);

return;

}
}

int main()
{
ChainBinTree *root = NULL; //root为指向二叉树根节点的指针
char select;
void (*oper1)(); //指向函数的指针
oper1 = oper; //指向具体操作的函数
do{
printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 0.退出\");
select = getch();
switch(select){
case '1':
root = initRoot();
break;
case '2':
addNode(root);
break;
case '3':
binTree_DLR(root, oper1);
printf(\"\\n\");
break;
case '4':
binTree_LDR(root, oper1);
printf(\"\\n\");
break;
case '5':
binTree_LRD(root, oper1);
printf(\"\\n\");
break;
case '6':
binTree_Level(root, oper1);
printf(\"\\n\");
break;
case '7':
printf(\"%d\", binTreeDepth(root));
break;
}
}while(select != '0');

binTreeClear(root);
root = NULL;

return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

二叉树:

\n
\n
二叉树性质:\n    1.在二叉树的第i层的结点总数最多有2^(i-1)个结点\n    2.深度为k的二叉树最多有2^(k) - 1个结点,最少有k个结点\n    3.二叉树,如果其叶结点为n0,而度为2的结点总数为n2,则n0=n2+1\n    4.有n个结点的完全二叉树的深度k为:k=[log2(n)]+1\n    5.有n个结点的完全二叉树各结点如果用顺序表存储,对任意结点i,有如下关系:\n        如果 i != 1,则其父节点的编号为i/2\n        如果 2*i <= n,则其左子树根节点的编号为2*i;若 2*i>n,则无左子树\n        如果 2*i+1 <= n,则右子树根节点的编号为2*i+1;若 2*i+1>n,则无右子树\n
\n

二叉树的存储:

\n
","more":"
1.顺序存储结构:若是完全二叉树,则某个结点的父节点=(该节点的位置/2),其子节点为(该节点的位置*2)和(该节点的位置*2+1),从1开始计算;\n                若不是完全二叉树,可以在没有结点数据的位置置为空,模拟成完全二叉树(然后同上);\n
\"图片说明\"\n
如图二叉树,如果使用顺序存储结则数据结构定义为如下sqTree数组:\n    #definde MAXSIZE 100\n    typedef int DATA;\n    typedef DATA SeqBinTree[MAXSIZE];\n    SeqBinTree sqTree;       //顺序存储结构\n对于上面的完全二叉树,最终使用顺序存储结构后,得到的效果为如下图:\n
\"图片说明\"\n
通过图中规律可发现:\n    1.求某个结点的子结点,只需把该结点在数组中的位置(非下标,而是下标+1)乘2,则子节点为所得的位置的结点以及其下一个结点;\n    2.求某个结点的父结点,只需把该结点在数组中的位置除以2,说的的商即为父结点位置;\n    3.对于非完全二叉树,可以模拟为完全二叉树使用,即把没有结点的位置“空出来”,表示没有结点,如图,假设I节点不存在,则9的位置即为空\n\n顺序存储结构问题:占用内存连续且必须提前分配足够的内存,不能扩容,不够灵活,所以一般使用链式存储结构存储树;\n
\"图片说明\"\n
2.二叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针(没有则赋值为空);\n\n    对于链式存储结构,可以使用树的基本原理,即树相当于多个子树嵌套,树中包含多个子树,而子树也是一个树结构,所以算法中经常涉及到递归调用,递归查询;\n    其中left指针存放左子树的根节点的地址,right指针存放右子树的根节点的地址;\n\n\n2.三叉链式存储结构:数据结构定义包括一个数据,一个指向左子树的指针,一个指向右子树的指针以及一个指向父结点的指针\n
\n

二叉树的遍历:

\n
\n
1.先序遍历(DLR): 顺序:根-左-右\n2.中序遍历(LDR): 顺序:左-根-右\n3.后序遍历(LRD)" 顺序:左-右-跟\n4.按层遍历\n

二叉顺序存储结构

二叉链式存储结构

\n

BinTree.h

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>

#define QUEUE_MAXSIZE 50

typedef char DATA; //定义树结点的元素类型

typedef struct ChainTree{ //定义二叉树结点类型
DATA data; //结点数据
struct ChainTree *left; //左子树结点指针
struct ChainTree *right;//右子树结点指针
}ChainBinTree;

/*初始化二叉树根节点*/
ChainBinTree *binTreeInit(ChainBinTree *node);

/*添加数据到二叉树*/
int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n);

/*返回左子节点和右子节点*/
ChainBinTree *binTreeLeft(ChainBinTree *bt);

ChainBinTree *binTreeRight(ChainBinTree *bt);

/*判断二叉树是否为空*/
int binTreeIsEmpty(ChainBinTree *bt);

/*求二叉树深度*/
int binTreeDepth(ChainBinTree *bt);

/*寻找值为data的结点*/
ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data);

/*清空树*/
void binTreeClear(ChainBinTree *bt);

/********************************树的遍历***************************/

/*遍历树要进行的操作*/
void oper(ChainBinTree *p);


/*先序遍历*/
void binTree_DLR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));

/*中序遍历*/
void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p));

/*后序遍历*/
void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p));

/*按层遍历*/
void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p));
\n
\n

BinTree.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include \"BinTree.h\"

/*初始化二叉树根节点*/
ChainBinTree *binTreeInit(ChainBinTree *node){
if(node != NULL)
return node;
else
return NULL;
}

/*添加数据到二叉树 bt为要添加的位置的父节点 node为添加的节点 n=1表示左子树 n=2表示右子树*/
int binTreeAddNode(ChainBinTree *bt, ChainBinTree *node, int n){
if(bt == NULL){
printf(\"父节点不存在!\\n\");
return 0;
}

switch(n){
//添加到左子树
case 1:
if(bt->left){
printf(\"左子树不为空, 不能添加!\\n\");
return 0;
}else{
bt->left = node;
}
break;
case 2:
if(bt->right){
printf(\"右子树不为空, 不能添加!\\n\");
return 0;
}else{
bt->right = node;
}
break;
default:
printf(\"参数错误\");
return 0;
}

return 1;
}

/*返回左子节点和右子节点*/
ChainBinTree *binTreeLeft(ChainBinTree *bt){
if(bt)
return bt->left;
else
return NULL;
}
ChainBinTree *binTreeRight(ChainBinTree *bt){
if(bt)
return bt->right;
else
return NULL;
}

/*判断二叉树是否为空*/
int binTreeIsEmpty(ChainBinTree *bt){
if(bt)
return 0;
else
return 1;

}

/*求二叉树深度*/
int binTreeDepth(ChainBinTree *bt){
int dep1,dep2;
if(bt == NULL){
return 0; //空树
}else{
//递归调用 递归子树直到最后的叶子结点没有子节点,返回0
dep1 = binTreeDepth(bt->left);
dep2 = binTreeDepth(bt->right);

//返回子树中深度更深的子树的深度
if(dep1 > dep2){
return dep1 + 1;
}else{
return dep2 + 1;
}
}
}

/*寻找值为data的结点*/
ChainBinTree *binTreeFind(ChainBinTree *bt, DATA data){
ChainBinTree *p;
if(bt == NULL)
return NULL;
else{
if(bt->data == data){
return bt;
}else{
//递归调用
if(p = binTreeFind(bt->left, data))
return p;
else if(p = binTreeFind(bt->right, data))
return p;
else
return NULL;
}
}
}

/*清空树*/
void binTreeClear(ChainBinTree *bt){
if(bt){
binTreeClear(bt->left);
binTreeClear(bt->right);
free(bt);
}

return;
}

/********************************树的遍历***************************/

/*遍历树要进行的操作*/
void oper(ChainBinTree *p){
printf(\"%c \\t\", p->data);
return;
}


/*先序遍历*/
void binTree_DLR(ChainBinTree *bt, void (*oper)(ChainBinTree *p)){
if(bt){
oper(bt);
binTree_DLR(bt->left, oper);
binTree_DLR(bt->right, oper);
}

return;
}

/*中序遍历*/
void binTree_LDR(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){
if(bt){
binTree_LDR(bt->left, oper);
oper(bt);
binTree_LDR(bt->right, oper);
}

return;
}

/*后序遍历*/
void binTree_LRD(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){
if(bt){
binTree_LRD(bt->left, oper);
binTree_LRD(bt->right, oper);
oper(bt);
}

return;
}

/*按层遍历*/
void binTree_Level(ChainBinTree *bt, void (*oper) (ChainBinTree *p)){
ChainBinTree *p;
ChainBinTree *q[QUEUE_MAXSIZE]; //定义一个顺序队列,先进先出
int head = 0, tail = 0; //队首队尾序号
if(bt){ //如果队首指针不为空
tail = (tail + 1) % QUEUE_MAXSIZE; //计算循环队列队尾序号
q[tail] = bt; //把二叉树根指针进队
}

//本质上:每次tail进行两次改变(只有当是完全二叉树,其他情况则有不同),然后head进行一次改变,实现按层把树的元素放到队列中去
//即每次把head指向的结点的两个子节点存放到队列,并且把该结点进行操作(执行oper方法);
//当tail不再增加,也就是说再也没有哪个未读取得结点还拥有子节点了,则剩下的就是把队列中剩余未操作的结点进行操作,直到队列为空,即head==tail,退出循环
while(head != tail){ //队列不为空, 进行循环
head = (head + 1) % QUEUE_MAXSIZE; //计算循环队列的队首序号
p = q[head]; //获取队列元素
oper(p); //处理队首元素

if(p->left != NULL){ //若结点存在左子树, 则左子树指针进队
tail = (tail + 1) % QUEUE_MAXSIZE;
q[tail] = p->left;
}

if(p->right != NULL){ //若结点存在右子树, 则右子树指针进队
tail = (tail + 1) % QUEUE_MAXSIZE;
q[tail] = p->right;
}
}

return;
}
\n\"图片说明\"\n

按层遍历过程。。。

\n
\n

main.c

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>
#include \"BinTree.h\"

ChainBinTree *initRoot(){
ChainBinTree *node;

if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){
printf(\"\\n输入根数据结点\");
scanf(\"%s\", &node->data);
node->left = NULL;
node->right = NULL;

return binTreeInit(node);
}

return NULL;
}

void addNode(ChainBinTree *bt){
ChainBinTree *node, *parent;//存放新增的结点和要挂接的父节点
DATA data;
char select;
if(node = (ChainBinTree *)malloc(sizeof(ChainBinTree))){//分配内存
printf(\"\\n输入二叉树结点数据:\");
fflush(stdin);
scanf(\"%s\", &node->data);
node->left = NULL;
node->right = NULL;

printf(\"输入父结点数据:\");
fflush(stdin);
scanf(\"%s\",&data);
parent = binTreeFind(bt, data);//查找结点
if(!parent){
printf(\"未找到结点\\n\");
free(node);
return;
}

printf(\"1.添加到左子树\\n2.添加到右子树\");
do{
select = getch();
select -= '0';
if(select == 1 || select == 2){
binTreeAddNode(parent, node, select);//添加结点到二叉树
}
}while(select != 1 && select != 2);

return;

}
}

int main()
{
ChainBinTree *root = NULL; //root为指向二叉树根节点的指针
char select;
void (*oper1)(); //指向函数的指针
oper1 = oper; //指向具体操作的函数
do{
printf(\"\\n1.设置二叉树根元素 2.添加二叉树结点 3.先序 4.中序 5.后序 6.按层 7.二叉树深度 0.退出\");
select = getch();
switch(select){
case '1':
root = initRoot();
break;
case '2':
addNode(root);
break;
case '3':
binTree_DLR(root, oper1);
printf(\"\\n\");
break;
case '4':
binTree_LDR(root, oper1);
printf(\"\\n\");
break;
case '5':
binTree_LRD(root, oper1);
printf(\"\\n\");
break;
case '6':
binTree_Level(root, oper1);
printf(\"\\n\");
break;
case '7':
printf(\"%d\", binTreeDepth(root));
break;
}
}while(select != '0');

binTreeClear(root);
root = NULL;

return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"栈实例(转载)","comments":1,"description":null,"_content":"\n> 栈:只允许在一端进行插入和删除操作,(后进先出)\n \n> 实现简单的四则运算 包括 +-*/以及()\n\n> 参考博客: http://blog.csdn.net/zhaishaojiang/article/details/40017791\n\n\n \n> 工程一共包含5个文件(由于代码不多,未分成头文件和实现)\n1. OptrStack.h OptrStack.c 操作符栈 包含基本的栈结构定义和操作方法\n2. OpndStack.h OpndStack.c 操作数栈 包含基本的栈结构定义和操作方法\n3. main.c : 测试文件 包含运算的方法\n\n\n 定义两个栈分别用于存储操作符和操作数;\n \n 栈所具有的功能有:置栈空,判栈空,入栈,出栈,取栈顶;\n \n 将输入的算术表达式存入字符数组中;\n \n 将表达式中的运算符划分优先级;\n \n 进行双目运算,即+,-,*,/;\n \n 将表达式压入栈中,并计算表达式结果。(思路:将操作符压入操作符栈中,将操作数压入操作数栈中。过程:判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,再将当前的操作符压入操作符栈中。)\n\n \n> #### OpndStack.h\n\n #include \n #include \n #define MAXSIZE2 100\n \n typedef struct\n {\n \tdouble date[MAXSIZE2];\n \tint top;\n }OpndStack;\t\t//操作数结构体\n \n /*-------操作数相关操作-------*/\n OpndStack *Init_OpndStack();/*置栈空*/\n int Empty_OpndStack(OpndStack *t);/*判空栈*/\n int Push_OpndStack(OpndStack *t, double y);/*入栈(注意:判断栈是否已满)*/\n double Pop_OpndStack(OpndStack *t);/*出栈(注意:判断栈是否已空)*/\n double GetTop_OpndStack(OpndStack *t);/*取栈顶元素*/\n\n\n> #### OpndStack.c\n\n #include \"OpndStack.h\"\n \n /*-------操作数相关操作-------*/\n \n OpndStack *Init_OpndStack()/*置栈空*/\n {\n \tOpndStack *t;\n \n \tt = (OpndStack *)malloc(sizeof(OpndStack));\n \tt->top = -1;\n \treturn t;\n }\n \n int Empty_OpndStack(OpndStack *t)/*判空栈*/\n {\n \tif(t->top == -1)\n \t{\n \t\treturn 1;\n \t}\n \telse\n \t{\n \t\treturn 0;\n \t}\n }\n \n int Push_OpndStack(OpndStack *t, double y)/*入栈(注意:判断栈是否已满)*/\n {\n \tif(t->top == MAXSIZE2 - 1)\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\tt->top ++;\n \t\tt->date[t->top] = y;\n \t\treturn 1;\n \t}\n }\n \n double Pop_OpndStack(OpndStack *t)/*出栈(注意:判断栈是否已空)*/\n {\n \tdouble y;\t\t//接收要出栈的元素\n \n \tif( Empty_OpndStack(t) )\n \t{\n \t\treturn 0;\n \t}\n \ty = t->date[t->top];\n \tt->top --;\n \treturn y;\n }\n \n double GetTop_OpndStack(OpndStack *t)/*取栈顶元素*/\n {\n \tif( Empty_OpndStack(t) )\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\treturn (t->date[t->top]);\n \t}\n }\n \n \n> #### OptrStack.h\n\n #include \n #include \n #define MAXSIZE1 100\n \n typedef struct\n {\n \tchar date[MAXSIZE1];\n \tint top;\n }OptrStack;\t\t//操作符结构体\n \n /*-------操作符相关操作-------*/\n OptrStack *Init_OptrStack();\t/*置栈空*/\n int Empty_OptrStack(OptrStack *s);/*判空栈*/\n int Push_OptrStack(OptrStack *s, char x);/*入栈(注意:判断栈是否已满)*/\n char Pop_OptrStack(OptrStack *s);/*出栈(注意:判断栈是否已空)*/\n char GetTop_OptrStack(OptrStack *s);/*取栈顶元素,先判空*/\n\n\n> #### OptrStack.c\n\n #include \"OptrStack.h\"\n \n /*-------操作符相关操作-------*/\n \n OptrStack *Init_OptrStack()\t/*置栈空*/\n {\n \tOptrStack *s;\n \n \ts = (OptrStack *)malloc(sizeof(OptrStack));\n \ts->top = -1;\n \treturn s;\n }\n \n int Empty_OptrStack(OptrStack *s)/*判空栈*/\n {\n \tif(s->top == -1)\n \t{\n \t\treturn 1;\t\t//如果栈为空,则返回真数\n \t}\n \telse\n \t{\n \t\treturn 0;\t\t//反之,返回零\n \t}\n }\n \n int Push_OptrStack(OptrStack *s, char x)/*入栈(注意:判断栈是否已满)*/\n {\n \tif(s->top == MAXSIZE1 - 1)\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\ts->top ++;\t\t//栈顶指针向上移,再赋值\n \t\ts->date[s->top] = x;\n \t\treturn 1;\n \t}\n }\n \n char Pop_OptrStack(OptrStack *s)/*出栈(注意:判断栈是否已空)*/\n {\n \tint x;\t\t//接收要出栈的元素\n \n \tif( Empty_OptrStack(s) )\n \t{\n \t\treturn 0;\n \t}\n \tx = s->date[s->top];\n \ts->top --;\n \treturn x;\n }\n \n char GetTop_OptrStack(OptrStack *s)/*取栈顶元素,先判空*/\n {\n \tif( Empty_OptrStack(s) )\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\treturn (s->date[s->top]);\n \t}\n }\n\n\n> #### main.c\n\n #include \n #include \n #include \"OpndStack.h\"\n #include \"OptrStack.h\"\n \n int Rank(char op);\t\t\t//划分运算符的优先级\n double Operate(double a, double b, char op);\t//运算操作\n void Handle_str(char str[]);\t\t//将储存表达式的字符数组压入栈内,并运算\n \n int main()\n {\n \tchar str[100];\n \n \tprintf(\"请输入算术表达式(功能:+,-,*,/。可带括号!):\\n\");\n \tscanf(\"%s\", str);\n \n \tHandle_str(str);\n \n \treturn 0;\n }\n \n int Rank(char op)\t\t\t//划分运算符的优先级\n {\n \tint x;\n \n \tswitch(op)\n \t{\n \tcase '#':\n \t\tx = 0;break;\n \tcase '(':\n \t\tx = 1;break;\n \tcase '+':\n \tcase '-':\n \t\tx = 2;break;\n \tcase '*':\n \tcase '/':\n \t\tx = 3;break;\n \t}\n \n \treturn x;\n }\n \n double Operate(double a, double b, char op)\t//运算操作\n {\n \tdouble c;\n \n \tswitch(op)\n \t{\n \tcase '+':\n \t\tc = a + b;break;\n \tcase '-':\n \t\tc = a - b;break;\n \tcase '*':\n \t\tc = a * b;break;\n \tcase '/':\n \t\tif(b == 0)\n \t\t{\n \t\t\tprintf(\"分母为零!\\n\");\n \t\t\treturn 0;\n \t\t}\n \t\telse\n \t\t\tc = a / b;break;\n \tdefault:\n \t\tprintf(\"输入的字符非法!\\n\");\n \t\tbreak;\n \t}\n \treturn c;\n }\n \n void Handle_str(char str[])\t\t//将储存表达式的字符数组压入栈内\n {\n \tOptrStack *optr = Init_OptrStack();\t//初始化操作符栈\n \tOpndStack *opnd = Init_OpndStack();\t\t//初始化操作数栈\n \tint i,j;\t\t//i,j为循环变量,a,b接收从操作数栈中出栈的元素\n \tdouble f,a,b;\t\t//接收将字符数转换为浮点数的值\n \tchar d[100];\t//储存字符串中连续的‘数’\n \tchar op;\t\t//接收从操作符栈中出栈的元素\n \n \tPush_OptrStack(optr, '#');\t//先往操作符栈中压入'#'\n \tfor (i = 0; str[i]; i++)\n \t{\n \t\tswitch(str[i]){\n \t\tcase '+':\n \t\tcase '-':\n \t\t\t/*先判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;\n 小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,\n 再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,\n 再将当前的操作符压入操作符栈中。*/\n \t\t\tif(GetTop_OptrStack(optr) == '#' || GetTop_OptrStack(optr) == '(')\n \t\t\t{\n \t\t\t\tPush_OptrStack(optr, str[i]);//入栈\n \t\t\t}\n \t\t\telse\n \t\t\t{\n \t\t\t\ta = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n \t\t\t\tb = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n \t\t\t\top = Pop_OptrStack(optr);//接收从操作符栈中出栈的元素\n \t\t\t\tPush_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n \t\t\t\tPush_OptrStack(optr, str[i]);\n \t\t\t}\n \t\t\tbreak;\n \t\tcase '*':\n \t\tcase '/':\n \t\t\tif(Rank(str[i]) > Rank(GetTop_OptrStack(optr)) || GetTop_OptrStack(optr) == '(')\n \t\t\t{\n \t\t\t\tPush_OptrStack(optr, str[i]);\n \t\t\t}\n \t\t\telse\n \t\t\t{\n \t\t\t\ta = Pop_OpndStack(opnd);\n \t\t\t\tb = Pop_OpndStack(opnd);\n \t\t\t\top = Pop_OptrStack(optr);\n \t\t\t\tPush_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n \t\t\t\tPush_OptrStack(optr, str[i]);\n \t\t\t}\n \t\t\tbreak;\n \t\tcase '(':\n \t\t\tPush_OptrStack(optr, str[i]);\n \t\t\tbreak;\n \t\tcase ')':\n \t\t\twhile(GetTop_OptrStack(optr) != '(')\n \t\t\t{\n \t\t\t\ta = Pop_OpndStack(opnd);\n \t\t\t\tb = Pop_OpndStack(opnd);\n \t\t\t\top = Pop_OptrStack(optr);\n \t\t\t\tPush_OpndStack(opnd, Operate(b, a, op)); //将计算后的值压入操作数栈中\n \t\t\t}\n \t\t\tPop_OptrStack(optr);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tj=0;\n \t\t\tdo{\n \t\t\t\td[j++]=str[i];\n \t\t\t\ti++;\n \t\t\t}while(str[i]>='0' && str[i]<='9'|| str[i]=='.'); //可存入一个或多个数字字符\n \t\t\td[j]='\\0'; //将输入的连续多个数字字符拼成了字符串\n \t\t\ti--;\n \t\t\tf=atof(d);\t//调用库函数atof()将字符数转换为浮点数\n \t\t\tPush_OpndStack(opnd, f);\t//将转换后的数压入操作数栈中\n \t\t\tbreak;\n \t\t}\n \n \t}\n \twhile(GetTop_OptrStack(optr) != '#')\n \t{\n \t\ta = Pop_OpndStack(opnd);\n \t\tb = Pop_OpndStack(opnd);\n \t\top = Pop_OptrStack(optr);\n \t\tPush_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n \t}\n \tprintf(\"表达式%s = %g\\n\", str, GetTop_OpndStack(opnd));//将操作数栈中的元素(即表达式的最终结果)打印出来\n }\n\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170817_z.md","raw":"---\ntitle: 栈实例(转载)\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> 栈:只允许在一端进行插入和删除操作,(后进先出)\n \n> 实现简单的四则运算 包括 +-*/以及()\n\n> 参考博客: http://blog.csdn.net/zhaishaojiang/article/details/40017791\n\n\n \n> 工程一共包含5个文件(由于代码不多,未分成头文件和实现)\n1. OptrStack.h OptrStack.c 操作符栈 包含基本的栈结构定义和操作方法\n2. OpndStack.h OpndStack.c 操作数栈 包含基本的栈结构定义和操作方法\n3. main.c : 测试文件 包含运算的方法\n\n\n 定义两个栈分别用于存储操作符和操作数;\n \n 栈所具有的功能有:置栈空,判栈空,入栈,出栈,取栈顶;\n \n 将输入的算术表达式存入字符数组中;\n \n 将表达式中的运算符划分优先级;\n \n 进行双目运算,即+,-,*,/;\n \n 将表达式压入栈中,并计算表达式结果。(思路:将操作符压入操作符栈中,将操作数压入操作数栈中。过程:判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,再将当前的操作符压入操作符栈中。)\n\n \n> #### OpndStack.h\n\n #include \n #include \n #define MAXSIZE2 100\n \n typedef struct\n {\n \tdouble date[MAXSIZE2];\n \tint top;\n }OpndStack;\t\t//操作数结构体\n \n /*-------操作数相关操作-------*/\n OpndStack *Init_OpndStack();/*置栈空*/\n int Empty_OpndStack(OpndStack *t);/*判空栈*/\n int Push_OpndStack(OpndStack *t, double y);/*入栈(注意:判断栈是否已满)*/\n double Pop_OpndStack(OpndStack *t);/*出栈(注意:判断栈是否已空)*/\n double GetTop_OpndStack(OpndStack *t);/*取栈顶元素*/\n\n\n> #### OpndStack.c\n\n #include \"OpndStack.h\"\n \n /*-------操作数相关操作-------*/\n \n OpndStack *Init_OpndStack()/*置栈空*/\n {\n \tOpndStack *t;\n \n \tt = (OpndStack *)malloc(sizeof(OpndStack));\n \tt->top = -1;\n \treturn t;\n }\n \n int Empty_OpndStack(OpndStack *t)/*判空栈*/\n {\n \tif(t->top == -1)\n \t{\n \t\treturn 1;\n \t}\n \telse\n \t{\n \t\treturn 0;\n \t}\n }\n \n int Push_OpndStack(OpndStack *t, double y)/*入栈(注意:判断栈是否已满)*/\n {\n \tif(t->top == MAXSIZE2 - 1)\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\tt->top ++;\n \t\tt->date[t->top] = y;\n \t\treturn 1;\n \t}\n }\n \n double Pop_OpndStack(OpndStack *t)/*出栈(注意:判断栈是否已空)*/\n {\n \tdouble y;\t\t//接收要出栈的元素\n \n \tif( Empty_OpndStack(t) )\n \t{\n \t\treturn 0;\n \t}\n \ty = t->date[t->top];\n \tt->top --;\n \treturn y;\n }\n \n double GetTop_OpndStack(OpndStack *t)/*取栈顶元素*/\n {\n \tif( Empty_OpndStack(t) )\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\treturn (t->date[t->top]);\n \t}\n }\n \n \n> #### OptrStack.h\n\n #include \n #include \n #define MAXSIZE1 100\n \n typedef struct\n {\n \tchar date[MAXSIZE1];\n \tint top;\n }OptrStack;\t\t//操作符结构体\n \n /*-------操作符相关操作-------*/\n OptrStack *Init_OptrStack();\t/*置栈空*/\n int Empty_OptrStack(OptrStack *s);/*判空栈*/\n int Push_OptrStack(OptrStack *s, char x);/*入栈(注意:判断栈是否已满)*/\n char Pop_OptrStack(OptrStack *s);/*出栈(注意:判断栈是否已空)*/\n char GetTop_OptrStack(OptrStack *s);/*取栈顶元素,先判空*/\n\n\n> #### OptrStack.c\n\n #include \"OptrStack.h\"\n \n /*-------操作符相关操作-------*/\n \n OptrStack *Init_OptrStack()\t/*置栈空*/\n {\n \tOptrStack *s;\n \n \ts = (OptrStack *)malloc(sizeof(OptrStack));\n \ts->top = -1;\n \treturn s;\n }\n \n int Empty_OptrStack(OptrStack *s)/*判空栈*/\n {\n \tif(s->top == -1)\n \t{\n \t\treturn 1;\t\t//如果栈为空,则返回真数\n \t}\n \telse\n \t{\n \t\treturn 0;\t\t//反之,返回零\n \t}\n }\n \n int Push_OptrStack(OptrStack *s, char x)/*入栈(注意:判断栈是否已满)*/\n {\n \tif(s->top == MAXSIZE1 - 1)\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\ts->top ++;\t\t//栈顶指针向上移,再赋值\n \t\ts->date[s->top] = x;\n \t\treturn 1;\n \t}\n }\n \n char Pop_OptrStack(OptrStack *s)/*出栈(注意:判断栈是否已空)*/\n {\n \tint x;\t\t//接收要出栈的元素\n \n \tif( Empty_OptrStack(s) )\n \t{\n \t\treturn 0;\n \t}\n \tx = s->date[s->top];\n \ts->top --;\n \treturn x;\n }\n \n char GetTop_OptrStack(OptrStack *s)/*取栈顶元素,先判空*/\n {\n \tif( Empty_OptrStack(s) )\n \t{\n \t\treturn 0;\n \t}\n \telse\n \t{\n \t\treturn (s->date[s->top]);\n \t}\n }\n\n\n> #### main.c\n\n #include \n #include \n #include \"OpndStack.h\"\n #include \"OptrStack.h\"\n \n int Rank(char op);\t\t\t//划分运算符的优先级\n double Operate(double a, double b, char op);\t//运算操作\n void Handle_str(char str[]);\t\t//将储存表达式的字符数组压入栈内,并运算\n \n int main()\n {\n \tchar str[100];\n \n \tprintf(\"请输入算术表达式(功能:+,-,*,/。可带括号!):\\n\");\n \tscanf(\"%s\", str);\n \n \tHandle_str(str);\n \n \treturn 0;\n }\n \n int Rank(char op)\t\t\t//划分运算符的优先级\n {\n \tint x;\n \n \tswitch(op)\n \t{\n \tcase '#':\n \t\tx = 0;break;\n \tcase '(':\n \t\tx = 1;break;\n \tcase '+':\n \tcase '-':\n \t\tx = 2;break;\n \tcase '*':\n \tcase '/':\n \t\tx = 3;break;\n \t}\n \n \treturn x;\n }\n \n double Operate(double a, double b, char op)\t//运算操作\n {\n \tdouble c;\n \n \tswitch(op)\n \t{\n \tcase '+':\n \t\tc = a + b;break;\n \tcase '-':\n \t\tc = a - b;break;\n \tcase '*':\n \t\tc = a * b;break;\n \tcase '/':\n \t\tif(b == 0)\n \t\t{\n \t\t\tprintf(\"分母为零!\\n\");\n \t\t\treturn 0;\n \t\t}\n \t\telse\n \t\t\tc = a / b;break;\n \tdefault:\n \t\tprintf(\"输入的字符非法!\\n\");\n \t\tbreak;\n \t}\n \treturn c;\n }\n \n void Handle_str(char str[])\t\t//将储存表达式的字符数组压入栈内\n {\n \tOptrStack *optr = Init_OptrStack();\t//初始化操作符栈\n \tOpndStack *opnd = Init_OpndStack();\t\t//初始化操作数栈\n \tint i,j;\t\t//i,j为循环变量,a,b接收从操作数栈中出栈的元素\n \tdouble f,a,b;\t\t//接收将字符数转换为浮点数的值\n \tchar d[100];\t//储存字符串中连续的‘数’\n \tchar op;\t\t//接收从操作符栈中出栈的元素\n \n \tPush_OptrStack(optr, '#');\t//先往操作符栈中压入'#'\n \tfor (i = 0; str[i]; i++)\n \t{\n \t\tswitch(str[i]){\n \t\tcase '+':\n \t\tcase '-':\n \t\t\t/*先判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;\n 小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,\n 再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,\n 再将当前的操作符压入操作符栈中。*/\n \t\t\tif(GetTop_OptrStack(optr) == '#' || GetTop_OptrStack(optr) == '(')\n \t\t\t{\n \t\t\t\tPush_OptrStack(optr, str[i]);//入栈\n \t\t\t}\n \t\t\telse\n \t\t\t{\n \t\t\t\ta = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n \t\t\t\tb = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n \t\t\t\top = Pop_OptrStack(optr);//接收从操作符栈中出栈的元素\n \t\t\t\tPush_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n \t\t\t\tPush_OptrStack(optr, str[i]);\n \t\t\t}\n \t\t\tbreak;\n \t\tcase '*':\n \t\tcase '/':\n \t\t\tif(Rank(str[i]) > Rank(GetTop_OptrStack(optr)) || GetTop_OptrStack(optr) == '(')\n \t\t\t{\n \t\t\t\tPush_OptrStack(optr, str[i]);\n \t\t\t}\n \t\t\telse\n \t\t\t{\n \t\t\t\ta = Pop_OpndStack(opnd);\n \t\t\t\tb = Pop_OpndStack(opnd);\n \t\t\t\top = Pop_OptrStack(optr);\n \t\t\t\tPush_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n \t\t\t\tPush_OptrStack(optr, str[i]);\n \t\t\t}\n \t\t\tbreak;\n \t\tcase '(':\n \t\t\tPush_OptrStack(optr, str[i]);\n \t\t\tbreak;\n \t\tcase ')':\n \t\t\twhile(GetTop_OptrStack(optr) != '(')\n \t\t\t{\n \t\t\t\ta = Pop_OpndStack(opnd);\n \t\t\t\tb = Pop_OpndStack(opnd);\n \t\t\t\top = Pop_OptrStack(optr);\n \t\t\t\tPush_OpndStack(opnd, Operate(b, a, op)); //将计算后的值压入操作数栈中\n \t\t\t}\n \t\t\tPop_OptrStack(optr);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tj=0;\n \t\t\tdo{\n \t\t\t\td[j++]=str[i];\n \t\t\t\ti++;\n \t\t\t}while(str[i]>='0' && str[i]<='9'|| str[i]=='.'); //可存入一个或多个数字字符\n \t\t\td[j]='\\0'; //将输入的连续多个数字字符拼成了字符串\n \t\t\ti--;\n \t\t\tf=atof(d);\t//调用库函数atof()将字符数转换为浮点数\n \t\t\tPush_OpndStack(opnd, f);\t//将转换后的数压入操作数栈中\n \t\t\tbreak;\n \t\t}\n \n \t}\n \twhile(GetTop_OptrStack(optr) != '#')\n \t{\n \t\ta = Pop_OpndStack(opnd);\n \t\tb = Pop_OpndStack(opnd);\n \t\top = Pop_OptrStack(optr);\n \t\tPush_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n \t}\n \tprintf(\"表达式%s = %g\\n\", str, GetTop_OpndStack(opnd));//将操作数栈中的元素(即表达式的最终结果)打印出来\n }\n\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170817_z","published":1,"date":"2020-01-15T05:50:39.627Z","updated":"2021-03-10T13:50:15.337Z","layout":"post","photos":[],"link":"","_id":"ckm3invdx007e24ujlj30hwai","content":"
\n

栈:只允许在一端进行插入和删除操作,(后进先出)

\n
\n
\n

实现简单的四则运算 包括 +-*/以及()

\n
\n
\n

参考博客: http://blog.csdn.net/zhaishaojiang/article/details/40017791

\n
\n\n
\n

工程一共包含5个文件(由于代码不多,未分成头文件和实现)

\n
    \n
  1. OptrStack.h OptrStack.c 操作符栈 包含基本的栈结构定义和操作方法
  2. \n
  3. OpndStack.h OpndStack.c 操作数栈 包含基本的栈结构定义和操作方法
  4. \n
  5. main.c : 测试文件 包含运算的方法
  6. \n
\n
\n
定义两个栈分别用于存储操作符和操作数;\n\n栈所具有的功能有:置栈空,判栈空,入栈,出栈,取栈顶;\n\n将输入的算术表达式存入字符数组中;\n\n将表达式中的运算符划分优先级;\n\n进行双目运算,即+,-,*,/;\n\n将表达式压入栈中,并计算表达式结果。(思路:将操作符压入操作符栈中,将操作数压入操作数栈中。过程:判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,再将当前的操作符压入操作符栈中。)\n
\n

OpndStack.h

\n
#include <stdio.h>\n#include <stdlib.h>\n#define MAXSIZE2 100\n\ntypedef struct\n{\n    double date[MAXSIZE2];\n    int top;\n}OpndStack;        //操作数结构体\n\n/*-------操作数相关操作-------*/\nOpndStack *Init_OpndStack();/*置栈空*/\nint Empty_OpndStack(OpndStack *t);/*判空栈*/\nint Push_OpndStack(OpndStack *t, double y);/*入栈(注意:判断栈是否已满)*/\ndouble Pop_OpndStack(OpndStack *t);/*出栈(注意:判断栈是否已空)*/\ndouble GetTop_OpndStack(OpndStack *t);/*取栈顶元素*/\n
\n

OpndStack.c

\n
#include "OpndStack.h"\n\n/*-------操作数相关操作-------*/\n\nOpndStack *Init_OpndStack()/*置栈空*/\n{\n    OpndStack *t;\n\n    t = (OpndStack *)malloc(sizeof(OpndStack));\n    t->top = -1;\n    return t;\n}\n\nint Empty_OpndStack(OpndStack *t)/*判空栈*/\n{\n    if(t->top == -1)\n    {\n        return 1;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nint Push_OpndStack(OpndStack *t, double y)/*入栈(注意:判断栈是否已满)*/\n{\n    if(t->top == MAXSIZE2 - 1)\n    {\n        return 0;\n    }\n    else\n    {\n        t->top ++;\n        t->date[t->top] = y;\n        return 1;\n    }\n}\n\ndouble Pop_OpndStack(OpndStack *t)/*出栈(注意:判断栈是否已空)*/\n{\n    double y;        //接收要出栈的元素\n\n    if( Empty_OpndStack(t) )\n    {\n        return 0;\n    }\n    y = t->date[t->top];\n    t->top --;\n    return y;\n}\n\ndouble GetTop_OpndStack(OpndStack *t)/*取栈顶元素*/\n{\n    if( Empty_OpndStack(t) )\n    {\n        return 0;\n    }\n    else\n    {\n        return (t->date[t->top]);\n    }\n}\n
\n

OptrStack.h

\n
#include <stdio.h>\n#include <stdlib.h>\n#define MAXSIZE1 100\n\ntypedef struct\n{\n    char date[MAXSIZE1];\n    int top;\n}OptrStack;        //操作符结构体\n\n/*-------操作符相关操作-------*/\nOptrStack *Init_OptrStack();    /*置栈空*/\nint Empty_OptrStack(OptrStack *s);/*判空栈*/\nint Push_OptrStack(OptrStack *s, char x);/*入栈(注意:判断栈是否已满)*/\nchar Pop_OptrStack(OptrStack *s);/*出栈(注意:判断栈是否已空)*/\nchar GetTop_OptrStack(OptrStack *s);/*取栈顶元素,先判空*/\n
\n

OptrStack.c

\n
#include "OptrStack.h"\n\n/*-------操作符相关操作-------*/\n\nOptrStack *Init_OptrStack()    /*置栈空*/\n{\n    OptrStack *s;\n\n    s = (OptrStack *)malloc(sizeof(OptrStack));\n    s->top = -1;\n    return s;\n}\n\nint Empty_OptrStack(OptrStack *s)/*判空栈*/\n{\n    if(s->top == -1)\n    {\n        return 1;        //如果栈为空,则返回真数\n    }\n    else\n    {\n        return 0;        //反之,返回零\n    }\n}\n\nint Push_OptrStack(OptrStack *s, char x)/*入栈(注意:判断栈是否已满)*/\n{\n    if(s->top == MAXSIZE1 - 1)\n    {\n        return 0;\n    }\n    else\n    {\n        s->top ++;        //栈顶指针向上移,再赋值\n        s->date[s->top] = x;\n        return 1;\n    }\n}\n\nchar Pop_OptrStack(OptrStack *s)/*出栈(注意:判断栈是否已空)*/\n{\n    int x;        //接收要出栈的元素\n\n    if( Empty_OptrStack(s) )\n    {\n        return 0;\n    }\n    x = s->date[s->top];\n    s->top --;\n    return x;\n}\n\nchar GetTop_OptrStack(OptrStack *s)/*取栈顶元素,先判空*/\n{\n    if( Empty_OptrStack(s) )\n    {\n        return 0;\n    }\n    else\n    {\n        return (s->date[s->top]);\n    }\n}\n
\n

main.c

\n
#include <stdio.h>\n#include <stdlib.h>\n#include "OpndStack.h"\n#include "OptrStack.h"\n\nint Rank(char op);            //划分运算符的优先级\ndouble Operate(double a, double b, char op);    //运算操作\nvoid Handle_str(char str[]);        //将储存表达式的字符数组压入栈内,并运算\n\nint main()\n{\n    char str[100];\n\n    printf("请输入算术表达式(功能:+,-,*,/。可带括号!):\\n");\n    scanf("%s", str);\n\n    Handle_str(str);\n\n    return 0;\n}\n\nint Rank(char op)            //划分运算符的优先级\n{\n    int x;\n\n    switch(op)\n    {\n    case '#':\n        x = 0;break;\n    case '(':\n        x = 1;break;\n    case '+':\n    case '-':\n        x = 2;break;\n    case '*':\n    case '/':\n        x = 3;break;\n    }\n\n    return x;\n}\n\ndouble Operate(double a, double b, char op)    //运算操作\n{\n    double c;\n\n    switch(op)\n    {\n    case '+':\n        c = a + b;break;\n    case '-':\n        c = a - b;break;\n    case '*':\n        c = a * b;break;\n    case '/':\n        if(b == 0)\n        {\n            printf("分母为零!\\n");\n            return 0;\n        }\n        else\n            c = a / b;break;\n    default:\n        printf("输入的字符非法!\\n");\n        break;\n    }\n    return c;\n}\n\nvoid Handle_str(char str[])        //将储存表达式的字符数组压入栈内\n{\n    OptrStack *optr = Init_OptrStack();    //初始化操作符栈\n    OpndStack *opnd = Init_OpndStack();        //初始化操作数栈\n    int i,j;        //i,j为循环变量,a,b接收从操作数栈中出栈的元素\n    double f,a,b;        //接收将字符数转换为浮点数的值\n    char d[100];    //储存字符串中连续的‘数’\n    char op;        //接收从操作符栈中出栈的元素\n\n    Push_OptrStack(optr, '#');    //先往操作符栈中压入'#'\n    for (i = 0; str[i]; i++)\n    {\n        switch(str[i]){\n        case '+':\n        case '-':\n            /*先判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;\n                小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,\n                再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,\n                再将当前的操作符压入操作符栈中。*/\n            if(GetTop_OptrStack(optr) == '#' || GetTop_OptrStack(optr) == '(')\n            {\n                Push_OptrStack(optr, str[i]);//入栈\n            }\n            else\n            {\n                a = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n                b = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n                op = Pop_OptrStack(optr);//接收从操作符栈中出栈的元素\n                Push_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n                Push_OptrStack(optr, str[i]);\n            }\n            break;\n        case '*':\n        case '/':\n            if(Rank(str[i]) > Rank(GetTop_OptrStack(optr)) || GetTop_OptrStack(optr) == '(')\n            {\n                Push_OptrStack(optr, str[i]);\n            }\n            else\n            {\n                a = Pop_OpndStack(opnd);\n                b = Pop_OpndStack(opnd);\n                op = Pop_OptrStack(optr);\n                Push_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n                Push_OptrStack(optr, str[i]);\n            }\n            break;\n        case '(':\n            Push_OptrStack(optr, str[i]);\n            break;\n        case ')':\n            while(GetTop_OptrStack(optr) != '(')\n            {\n                a = Pop_OpndStack(opnd);\n                b = Pop_OpndStack(opnd);\n                op = Pop_OptrStack(optr);\n                Push_OpndStack(opnd, Operate(b, a, op)); //将计算后的值压入操作数栈中\n            }\n            Pop_OptrStack(optr);\n            break;\n        default:\n            j=0;\n            do{\n                d[j++]=str[i];\n                i++;\n            }while(str[i]>='0' && str[i]<='9'|| str[i]=='.');  //可存入一个或多个数字字符\n            d[j]='\\0';     //将输入的连续多个数字字符拼成了字符串\n            i--;\n            f=atof(d);    //调用库函数atof()将字符数转换为浮点数\n            Push_OpndStack(opnd, f);    //将转换后的数压入操作数栈中\n            break;\n        }\n\n    }\n    while(GetTop_OptrStack(optr) != '#')\n    {\n        a = Pop_OpndStack(opnd);\n        b = Pop_OpndStack(opnd);\n        op = Pop_OptrStack(optr);\n        Push_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n    }\n    printf("表达式%s = %g\\n", str, GetTop_OpndStack(opnd));//将操作数栈中的元素(即表达式的最终结果)打印出来\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

栈:只允许在一端进行插入和删除操作,(后进先出)

\n
\n
\n

实现简单的四则运算 包括 +-*/以及()

\n
\n
\n

参考博客: http://blog.csdn.net/zhaishaojiang/article/details/40017791

\n
","more":"
\n

工程一共包含5个文件(由于代码不多,未分成头文件和实现)

\n
    \n
  1. OptrStack.h OptrStack.c 操作符栈 包含基本的栈结构定义和操作方法
  2. \n
  3. OpndStack.h OpndStack.c 操作数栈 包含基本的栈结构定义和操作方法
  4. \n
  5. main.c : 测试文件 包含运算的方法
  6. \n
\n
\n
定义两个栈分别用于存储操作符和操作数;\n\n栈所具有的功能有:置栈空,判栈空,入栈,出栈,取栈顶;\n\n将输入的算术表达式存入字符数组中;\n\n将表达式中的运算符划分优先级;\n\n进行双目运算,即+,-,*,/;\n\n将表达式压入栈中,并计算表达式结果。(思路:将操作符压入操作符栈中,将操作数压入操作数栈中。过程:判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,再将当前的操作符压入操作符栈中。)\n
\n

OpndStack.h

\n
#include <stdio.h>\n#include <stdlib.h>\n#define MAXSIZE2 100\n\ntypedef struct\n{\n    double date[MAXSIZE2];\n    int top;\n}OpndStack;        //操作数结构体\n\n/*-------操作数相关操作-------*/\nOpndStack *Init_OpndStack();/*置栈空*/\nint Empty_OpndStack(OpndStack *t);/*判空栈*/\nint Push_OpndStack(OpndStack *t, double y);/*入栈(注意:判断栈是否已满)*/\ndouble Pop_OpndStack(OpndStack *t);/*出栈(注意:判断栈是否已空)*/\ndouble GetTop_OpndStack(OpndStack *t);/*取栈顶元素*/\n
\n

OpndStack.c

\n
#include "OpndStack.h"\n\n/*-------操作数相关操作-------*/\n\nOpndStack *Init_OpndStack()/*置栈空*/\n{\n    OpndStack *t;\n\n    t = (OpndStack *)malloc(sizeof(OpndStack));\n    t->top = -1;\n    return t;\n}\n\nint Empty_OpndStack(OpndStack *t)/*判空栈*/\n{\n    if(t->top == -1)\n    {\n        return 1;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nint Push_OpndStack(OpndStack *t, double y)/*入栈(注意:判断栈是否已满)*/\n{\n    if(t->top == MAXSIZE2 - 1)\n    {\n        return 0;\n    }\n    else\n    {\n        t->top ++;\n        t->date[t->top] = y;\n        return 1;\n    }\n}\n\ndouble Pop_OpndStack(OpndStack *t)/*出栈(注意:判断栈是否已空)*/\n{\n    double y;        //接收要出栈的元素\n\n    if( Empty_OpndStack(t) )\n    {\n        return 0;\n    }\n    y = t->date[t->top];\n    t->top --;\n    return y;\n}\n\ndouble GetTop_OpndStack(OpndStack *t)/*取栈顶元素*/\n{\n    if( Empty_OpndStack(t) )\n    {\n        return 0;\n    }\n    else\n    {\n        return (t->date[t->top]);\n    }\n}\n
\n

OptrStack.h

\n
#include <stdio.h>\n#include <stdlib.h>\n#define MAXSIZE1 100\n\ntypedef struct\n{\n    char date[MAXSIZE1];\n    int top;\n}OptrStack;        //操作符结构体\n\n/*-------操作符相关操作-------*/\nOptrStack *Init_OptrStack();    /*置栈空*/\nint Empty_OptrStack(OptrStack *s);/*判空栈*/\nint Push_OptrStack(OptrStack *s, char x);/*入栈(注意:判断栈是否已满)*/\nchar Pop_OptrStack(OptrStack *s);/*出栈(注意:判断栈是否已空)*/\nchar GetTop_OptrStack(OptrStack *s);/*取栈顶元素,先判空*/\n
\n

OptrStack.c

\n
#include "OptrStack.h"\n\n/*-------操作符相关操作-------*/\n\nOptrStack *Init_OptrStack()    /*置栈空*/\n{\n    OptrStack *s;\n\n    s = (OptrStack *)malloc(sizeof(OptrStack));\n    s->top = -1;\n    return s;\n}\n\nint Empty_OptrStack(OptrStack *s)/*判空栈*/\n{\n    if(s->top == -1)\n    {\n        return 1;        //如果栈为空,则返回真数\n    }\n    else\n    {\n        return 0;        //反之,返回零\n    }\n}\n\nint Push_OptrStack(OptrStack *s, char x)/*入栈(注意:判断栈是否已满)*/\n{\n    if(s->top == MAXSIZE1 - 1)\n    {\n        return 0;\n    }\n    else\n    {\n        s->top ++;        //栈顶指针向上移,再赋值\n        s->date[s->top] = x;\n        return 1;\n    }\n}\n\nchar Pop_OptrStack(OptrStack *s)/*出栈(注意:判断栈是否已空)*/\n{\n    int x;        //接收要出栈的元素\n\n    if( Empty_OptrStack(s) )\n    {\n        return 0;\n    }\n    x = s->date[s->top];\n    s->top --;\n    return x;\n}\n\nchar GetTop_OptrStack(OptrStack *s)/*取栈顶元素,先判空*/\n{\n    if( Empty_OptrStack(s) )\n    {\n        return 0;\n    }\n    else\n    {\n        return (s->date[s->top]);\n    }\n}\n
\n

main.c

\n
#include <stdio.h>\n#include <stdlib.h>\n#include "OpndStack.h"\n#include "OptrStack.h"\n\nint Rank(char op);            //划分运算符的优先级\ndouble Operate(double a, double b, char op);    //运算操作\nvoid Handle_str(char str[]);        //将储存表达式的字符数组压入栈内,并运算\n\nint main()\n{\n    char str[100];\n\n    printf("请输入算术表达式(功能:+,-,*,/。可带括号!):\\n");\n    scanf("%s", str);\n\n    Handle_str(str);\n\n    return 0;\n}\n\nint Rank(char op)            //划分运算符的优先级\n{\n    int x;\n\n    switch(op)\n    {\n    case '#':\n        x = 0;break;\n    case '(':\n        x = 1;break;\n    case '+':\n    case '-':\n        x = 2;break;\n    case '*':\n    case '/':\n        x = 3;break;\n    }\n\n    return x;\n}\n\ndouble Operate(double a, double b, char op)    //运算操作\n{\n    double c;\n\n    switch(op)\n    {\n    case '+':\n        c = a + b;break;\n    case '-':\n        c = a - b;break;\n    case '*':\n        c = a * b;break;\n    case '/':\n        if(b == 0)\n        {\n            printf("分母为零!\\n");\n            return 0;\n        }\n        else\n            c = a / b;break;\n    default:\n        printf("输入的字符非法!\\n");\n        break;\n    }\n    return c;\n}\n\nvoid Handle_str(char str[])        //将储存表达式的字符数组压入栈内\n{\n    OptrStack *optr = Init_OptrStack();    //初始化操作符栈\n    OpndStack *opnd = Init_OpndStack();        //初始化操作数栈\n    int i,j;        //i,j为循环变量,a,b接收从操作数栈中出栈的元素\n    double f,a,b;        //接收将字符数转换为浮点数的值\n    char d[100];    //储存字符串中连续的‘数’\n    char op;        //接收从操作符栈中出栈的元素\n\n    Push_OptrStack(optr, '#');    //先往操作符栈中压入'#'\n    for (i = 0; str[i]; i++)\n    {\n        switch(str[i]){\n        case '+':\n        case '-':\n            /*先判断当前运算符与操作符栈栈顶元素的优先级,如果高于栈顶元素,则入栈;\n                小于栈顶元素,则从操作数栈中依次出两个数,并将操作符栈中栈顶元素出栈,\n                再将从操作数栈中出的两个数,按从操作符栈栈中出的运算符运算,并将结果压入操作数栈中,\n                再将当前的操作符压入操作符栈中。*/\n            if(GetTop_OptrStack(optr) == '#' || GetTop_OptrStack(optr) == '(')\n            {\n                Push_OptrStack(optr, str[i]);//入栈\n            }\n            else\n            {\n                a = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n                b = Pop_OpndStack(opnd);//接收从操作数栈中出栈的元素\n                op = Pop_OptrStack(optr);//接收从操作符栈中出栈的元素\n                Push_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n                Push_OptrStack(optr, str[i]);\n            }\n            break;\n        case '*':\n        case '/':\n            if(Rank(str[i]) > Rank(GetTop_OptrStack(optr)) || GetTop_OptrStack(optr) == '(')\n            {\n                Push_OptrStack(optr, str[i]);\n            }\n            else\n            {\n                a = Pop_OpndStack(opnd);\n                b = Pop_OpndStack(opnd);\n                op = Pop_OptrStack(optr);\n                Push_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n                Push_OptrStack(optr, str[i]);\n            }\n            break;\n        case '(':\n            Push_OptrStack(optr, str[i]);\n            break;\n        case ')':\n            while(GetTop_OptrStack(optr) != '(')\n            {\n                a = Pop_OpndStack(opnd);\n                b = Pop_OpndStack(opnd);\n                op = Pop_OptrStack(optr);\n                Push_OpndStack(opnd, Operate(b, a, op)); //将计算后的值压入操作数栈中\n            }\n            Pop_OptrStack(optr);\n            break;\n        default:\n            j=0;\n            do{\n                d[j++]=str[i];\n                i++;\n            }while(str[i]>='0' && str[i]<='9'|| str[i]=='.');  //可存入一个或多个数字字符\n            d[j]='\\0';     //将输入的连续多个数字字符拼成了字符串\n            i--;\n            f=atof(d);    //调用库函数atof()将字符数转换为浮点数\n            Push_OpndStack(opnd, f);    //将转换后的数压入操作数栈中\n            break;\n        }\n\n    }\n    while(GetTop_OptrStack(optr) != '#')\n    {\n        a = Pop_OpndStack(opnd);\n        b = Pop_OpndStack(opnd);\n        op = Pop_OptrStack(optr);\n        Push_OpndStack(opnd, Operate(b, a, op));//将计算后的值压入操作数栈中\n    }\n    printf("表达式%s = %g\\n", str, GetTop_OpndStack(opnd));//将操作数栈中的元素(即表达式的最终结果)打印出来\n}\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"哈夫曼(霍夫曼)树以及哈夫曼编码","comments":1,"description":null,"_content":"\n> 哈夫曼树:又称最优二叉树,是一种带权路径长度最短的二叉树;\n> 哈夫曼编码:哈夫曼树的一个应用,如JPEG中就应用;\n \n 所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度\n (若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。\n 树的带权路径长度记为WPL = (W1*L1+W2*L2+W3*L3+...+Wn*Ln),\n N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。\n 可以证明哈夫曼树的WPL是最小的。\n \n 一般权值用来表示频率大小,频率越大则权值越高\n \n \n\n\n> 参考博客1: http://www.cnblogs.com/junyuhuang/p/4127095.html\n\n> 参考博客2: http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html\n\n\n\n> ####哈弗曼编码原理(转载)\n\n   哈夫曼编码使用一种特别的方法为信号源中的每个符号设定二进制码。出现频率更大的符号将获得更短的比特,出现频率更小的符号将被分配更长的比特,以此来提高数据压缩率,提高传输效率。具体编码步骤主要为,\n \n   1、统计:\n \n   在开始编码时,通常都需要对信号源,也就是本文的一段文字,进行处理,计算出每个符号出现的频率,得到信号源的基本情况。接下来就是对统计信息进行处理了\n \n   \n \n   2、构造优先对列:\n \n   把得到的符号添加到优先队列中,此优先队列的进出逻辑是频率低的先出,因此在设计优先队列时需要如此设计,如果不熟悉优先队列,请阅读相关书籍,在此不做过多概述。得到包含所有字符的优先队列后,就是处理优先队列中的数据了。\n \n \n   3、构造哈夫曼树:\n \n   哈夫曼树是带权值得二叉树,我们使用的哈夫曼树的权值自然就是符号的频率了,我们构建哈夫曼树是自底向上的,先构建叶子节点,然后逐步向上,最终完成整颗树。先把队列中的一个符号出列,也就是最小频率的符号,,然后再出列一个符号。这两个符号将作为哈夫曼树的节点,而且这两个节点将作为新节点,也就是它们父节点,的左右孩子节点。新节点的频率,即权值,为孩子节点的和。把这个新节点添加到队列中(队列会重新根据权值排序)。重复上面的步骤,两个符号出列,构造新的父节点,入列……直到队列最后只剩下一个节点,这个节点也就是哈夫曼树的根节点了。\n \n   \n \n   4、为哈弗曼树编码:\n \n   哈夫曼树的来自信号源的符号都是叶子节点,需要知道下。树的根节点分配比特0,左子树分配0,右字数分配1。然后就可以得到符号的码值了。\n \n \n\n> ####哈夫曼编码步骤 \n\n1.首先构建一个元素为哈夫曼树结点的数组用于存储哈夫曼树(线性);\n \n 哈夫曼树数据结构包括元素数据、权值、父结点位置(数组下标)、两个子结点位置\n \n typedef struct\n {\n int weight;\n int parent;\n int lchild;\n int rchild;\n int value;\n } HNodeType\n\n2.初始化数组,设置树元素的默认属性;然后输入要编码的数据及其权值,存储到数组前几位,这几个即为哈夫曼树的叶子结点;\n \n 哈夫曼树中有效的数据仅仅是叶子结点,\n 而非叶子结点是为了构建哈夫曼树而加入的\n\n3.开始构建哈夫曼树:找出整个数组中有数据且权值最低的两个作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和;\n 然后把新构建的根结点存放到数中空位置,然后继续从整个数组中寻找。。。直到有效元素全部读取。\n\n4.编码,树的根节点分配比特0,左子树分配0,右字数分配1;\n\n5.解码,思路就是 把要解码的字符串像学中学方程一样代入到哈夫曼树中,按照左子树分配0,右字数分配1的原则,一个一个遍历出来\n\n---\n---\n\n 示例(转载):\n \n   假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,\n 那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:\n \n  {% qnimg 201112231832078695.png title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n \n   虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:\n \n  {% qnimg 201112231832087092.png title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n \n 再依次建立哈夫曼树,如下图:\n \n  {% qnimg 201112231832084301.jpg title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n \n 其中各个权值替换对应的字符即为下图:\n \n  {% qnimg 201112231832086286.jpg title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n 如下图也可以加深大家的理解:\n\n  {% qnimg 272122409653995.gif title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n---\n---\n\n> 代码实例\n\n```c\n/*-------------------------------------------------------------------------\n * Name: 哈夫曼编码源代码。\n * Date: 2011.04.16\n * Author: Jeffrey Hill+Jezze(解码部分)\n * 在 Win-TC 下测试通过\n * 实现过程:着先通过 HuffmanTree() 函数构造哈夫曼树,然后在主函数 main()中\n * 自底向上开始(也就是从数组序号为零的结点开始)向上层层判断,若在\n * 父结点左侧,则置码为 0,若在右侧,则置码为 1。最后输出生成的编码。\n *------------------------------------------------------------------------*/\n\n\n//结点个数 = 叶子结点个数 * 2 - 1\n#include \n#include\n\n#define MAXBIT 100\n#define MAXVALUE 10000\n#define MAXLEAF 30\n#define MAXNODE MAXLEAF*2 -1\n\ntypedef struct\n{\n int bit[MAXBIT]; //结点的编码\n int start; //结点编码的起始位,有效位置,如当start=2, 则该结点的编码从bit[2]开始\n} HCodeType; /* 编码结构体 */\n\ntypedef struct\n{\n int weight;\n int parent;\n int lchild;\n int rchild;\n int value;\n} HNodeType; /* 结点结构体 */\n\n/* 构造一颗哈夫曼树 */\nvoid HuffmanTree(HNodeType HuffNode[MAXNODE], int n)\n{\n /* i、j: 循环变量,\n m1、m2:构造哈夫曼树不同过程中两个最小权值结点的权值,\n x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号。*/\n int i, j, m1, m2, x1, x2;\n /* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */\n for(i = 0; i < 2 * n - 1; i++)\n {\n HuffNode[i].weight = 0;//权值\n HuffNode[i].parent = -1;//父结点位置初始化为-1,表示没有父结点,到时候要通过该属性进行判断,排除掉已加入到树的元素\n HuffNode[i].lchild = -1;//初始化左子树根节点位置\n HuffNode[i].rchild = -1;//初始化右子树根节点位置\n HuffNode[i].value = i; //实际值,可根据情况替换为字母\n } /* end for */\n\n /* 用户输入 n 个叶子结点的权值 */\n for(i = 0; i < n; i++)\n {\n printf(\"请输入第 %d 个元素的权值: \\n\", i);\n scanf(\"%d\", &HuffNode[i].weight);\n } /* end for */\n\n /* 循环构造 Huffman 树 */\n for(i = 0; i < n - 1; i++) // 循环叶子结点的个数,一次循环要形成一个“新”的二叉树\n {\n m1 = m2 = MAXVALUE; //m1、m2中存放两个无父结点且结点权值最小的两个结点\n x1 = x2 = 0;\n //找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树\n for(j = 0; j < n + i; j++) //循环数组中元素个数\n {\n //要找到当前数组中所有的元素中最小的两位,处理之后又把结果存到数组中所以要(n+i)\n if(HuffNode[j].weight < m1 && HuffNode[j].parent == -1)\n {\n //当前的权值更小,把当前元素赋值给m1,把原来m1的赋值给m2\n m2 = m1;\n x2 = x1;\n m1 = HuffNode[j].weight;\n x1 = j;\n }\n else if(HuffNode[j].weight < m2 && HuffNode[j].parent == -1)\n {\n //当前的权值大于m1但是小于m2,把其赋值给m2\n m2 = HuffNode[j].weight;\n x2 = j;\n }\n } /* end for */\n /* 最终,m1每次存放最小权值,m2存放次小的 */\n\n /* 设置 找到的两个子结点 x1、x2 的父结点信息 */\n /* 父结点存放到数组的下标为(n + i)的位置 */\n HuffNode[x1].parent = n + i;\n HuffNode[x2].parent = n + i;\n /* 设置 父结点的属性 */\n HuffNode[n + i].weight = HuffNode[x1].weight + HuffNode[x2].weight;\n HuffNode[n + i].lchild = x1;\n HuffNode[n + i].rchild = x2;\n\n printf(\"第 %d 次循环的两个结果的权值为: %d, %d\\n\", i + 1, HuffNode[x1].weight, HuffNode[x2].weight); /* 用于测试 */\n printf(\"\\n\");\n } /* end for */\n /* for(i=0;i>>> 因为“左子树分配0,右字数分配1”\n cd.bit[cd.start] = 1;\n cd.start--; /* 求编码的低一位 */\n\n /* 设置下一循环条件 */\n c = p; //设置c为父结点的下标 准备进行下次while循环,则那时候的“当前的结点”就会变成现在结点的父结点\n p = HuffNode[c].parent; //同理\n\n }/* end while */\n\n /* 保存 求出的每个叶结点的哈夫曼编码和编码的起始位 */\n for(j = cd.start + 1; j < n; j++)\n {\n HuffCode[i].bit[j] = cd.bit[j];\n }\n HuffCode[i].start = cd.start + 1; //编码的起始位\n\n } /* end for */\n /*对哈夫曼树进行编码结束*/\n\n /* 打印已保存好的所有存在编码的哈夫曼编码 */\n for(i = 0; i < n; i++)\n {\n printf(\"第%d位置的树结点的编码为:: \", i);\n for(j = HuffCode[i].start; j < n; j++)//从有效位置开始输出该结点的编码\n {\n printf(\"%d\", HuffCode[i].bit[j]);\n }\n printf(\" 结点的属性bit数组中有效的编码开始位置为:bit[%d]\\n\", HuffCode[i].start);\n }\n /* 打印结束 */\n\n printf(\"Decoding?Please Enter code:\\n\");\n scanf(\"%s\", &pp);\n decodeing(pp, HuffNode, n);\n getch();\n return 0;\n}\n\n\n```\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170908_hfms.md","raw":"---\ntitle: 哈夫曼(霍夫曼)树以及哈夫曼编码\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> 哈夫曼树:又称最优二叉树,是一种带权路径长度最短的二叉树;\n> 哈夫曼编码:哈夫曼树的一个应用,如JPEG中就应用;\n \n 所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度\n (若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。\n 树的带权路径长度记为WPL = (W1*L1+W2*L2+W3*L3+...+Wn*Ln),\n N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。\n 可以证明哈夫曼树的WPL是最小的。\n \n 一般权值用来表示频率大小,频率越大则权值越高\n \n \n\n\n> 参考博客1: http://www.cnblogs.com/junyuhuang/p/4127095.html\n\n> 参考博客2: http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html\n\n\n\n> ####哈弗曼编码原理(转载)\n\n   哈夫曼编码使用一种特别的方法为信号源中的每个符号设定二进制码。出现频率更大的符号将获得更短的比特,出现频率更小的符号将被分配更长的比特,以此来提高数据压缩率,提高传输效率。具体编码步骤主要为,\n \n   1、统计:\n \n   在开始编码时,通常都需要对信号源,也就是本文的一段文字,进行处理,计算出每个符号出现的频率,得到信号源的基本情况。接下来就是对统计信息进行处理了\n \n   \n \n   2、构造优先对列:\n \n   把得到的符号添加到优先队列中,此优先队列的进出逻辑是频率低的先出,因此在设计优先队列时需要如此设计,如果不熟悉优先队列,请阅读相关书籍,在此不做过多概述。得到包含所有字符的优先队列后,就是处理优先队列中的数据了。\n \n \n   3、构造哈夫曼树:\n \n   哈夫曼树是带权值得二叉树,我们使用的哈夫曼树的权值自然就是符号的频率了,我们构建哈夫曼树是自底向上的,先构建叶子节点,然后逐步向上,最终完成整颗树。先把队列中的一个符号出列,也就是最小频率的符号,,然后再出列一个符号。这两个符号将作为哈夫曼树的节点,而且这两个节点将作为新节点,也就是它们父节点,的左右孩子节点。新节点的频率,即权值,为孩子节点的和。把这个新节点添加到队列中(队列会重新根据权值排序)。重复上面的步骤,两个符号出列,构造新的父节点,入列……直到队列最后只剩下一个节点,这个节点也就是哈夫曼树的根节点了。\n \n   \n \n   4、为哈弗曼树编码:\n \n   哈夫曼树的来自信号源的符号都是叶子节点,需要知道下。树的根节点分配比特0,左子树分配0,右字数分配1。然后就可以得到符号的码值了。\n \n \n\n> ####哈夫曼编码步骤 \n\n1.首先构建一个元素为哈夫曼树结点的数组用于存储哈夫曼树(线性);\n \n 哈夫曼树数据结构包括元素数据、权值、父结点位置(数组下标)、两个子结点位置\n \n typedef struct\n {\n int weight;\n int parent;\n int lchild;\n int rchild;\n int value;\n } HNodeType\n\n2.初始化数组,设置树元素的默认属性;然后输入要编码的数据及其权值,存储到数组前几位,这几个即为哈夫曼树的叶子结点;\n \n 哈夫曼树中有效的数据仅仅是叶子结点,\n 而非叶子结点是为了构建哈夫曼树而加入的\n\n3.开始构建哈夫曼树:找出整个数组中有数据且权值最低的两个作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和;\n 然后把新构建的根结点存放到数中空位置,然后继续从整个数组中寻找。。。直到有效元素全部读取。\n\n4.编码,树的根节点分配比特0,左子树分配0,右字数分配1;\n\n5.解码,思路就是 把要解码的字符串像学中学方程一样代入到哈夫曼树中,按照左子树分配0,右字数分配1的原则,一个一个遍历出来\n\n---\n---\n\n 示例(转载):\n \n   假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,\n 那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:\n \n  {% qnimg 201112231832078695.png title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n \n   虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:\n \n  {% qnimg 201112231832087092.png title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n \n 再依次建立哈夫曼树,如下图:\n \n  {% qnimg 201112231832084301.jpg title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n \n 其中各个权值替换对应的字符即为下图:\n \n  {% qnimg 201112231832086286.jpg title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n 如下图也可以加深大家的理解:\n\n  {% qnimg 272122409653995.gif title:哈夫曼树 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n---\n---\n\n> 代码实例\n\n```c\n/*-------------------------------------------------------------------------\n * Name: 哈夫曼编码源代码。\n * Date: 2011.04.16\n * Author: Jeffrey Hill+Jezze(解码部分)\n * 在 Win-TC 下测试通过\n * 实现过程:着先通过 HuffmanTree() 函数构造哈夫曼树,然后在主函数 main()中\n * 自底向上开始(也就是从数组序号为零的结点开始)向上层层判断,若在\n * 父结点左侧,则置码为 0,若在右侧,则置码为 1。最后输出生成的编码。\n *------------------------------------------------------------------------*/\n\n\n//结点个数 = 叶子结点个数 * 2 - 1\n#include \n#include\n\n#define MAXBIT 100\n#define MAXVALUE 10000\n#define MAXLEAF 30\n#define MAXNODE MAXLEAF*2 -1\n\ntypedef struct\n{\n int bit[MAXBIT]; //结点的编码\n int start; //结点编码的起始位,有效位置,如当start=2, 则该结点的编码从bit[2]开始\n} HCodeType; /* 编码结构体 */\n\ntypedef struct\n{\n int weight;\n int parent;\n int lchild;\n int rchild;\n int value;\n} HNodeType; /* 结点结构体 */\n\n/* 构造一颗哈夫曼树 */\nvoid HuffmanTree(HNodeType HuffNode[MAXNODE], int n)\n{\n /* i、j: 循环变量,\n m1、m2:构造哈夫曼树不同过程中两个最小权值结点的权值,\n x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号。*/\n int i, j, m1, m2, x1, x2;\n /* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */\n for(i = 0; i < 2 * n - 1; i++)\n {\n HuffNode[i].weight = 0;//权值\n HuffNode[i].parent = -1;//父结点位置初始化为-1,表示没有父结点,到时候要通过该属性进行判断,排除掉已加入到树的元素\n HuffNode[i].lchild = -1;//初始化左子树根节点位置\n HuffNode[i].rchild = -1;//初始化右子树根节点位置\n HuffNode[i].value = i; //实际值,可根据情况替换为字母\n } /* end for */\n\n /* 用户输入 n 个叶子结点的权值 */\n for(i = 0; i < n; i++)\n {\n printf(\"请输入第 %d 个元素的权值: \\n\", i);\n scanf(\"%d\", &HuffNode[i].weight);\n } /* end for */\n\n /* 循环构造 Huffman 树 */\n for(i = 0; i < n - 1; i++) // 循环叶子结点的个数,一次循环要形成一个“新”的二叉树\n {\n m1 = m2 = MAXVALUE; //m1、m2中存放两个无父结点且结点权值最小的两个结点\n x1 = x2 = 0;\n //找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树\n for(j = 0; j < n + i; j++) //循环数组中元素个数\n {\n //要找到当前数组中所有的元素中最小的两位,处理之后又把结果存到数组中所以要(n+i)\n if(HuffNode[j].weight < m1 && HuffNode[j].parent == -1)\n {\n //当前的权值更小,把当前元素赋值给m1,把原来m1的赋值给m2\n m2 = m1;\n x2 = x1;\n m1 = HuffNode[j].weight;\n x1 = j;\n }\n else if(HuffNode[j].weight < m2 && HuffNode[j].parent == -1)\n {\n //当前的权值大于m1但是小于m2,把其赋值给m2\n m2 = HuffNode[j].weight;\n x2 = j;\n }\n } /* end for */\n /* 最终,m1每次存放最小权值,m2存放次小的 */\n\n /* 设置 找到的两个子结点 x1、x2 的父结点信息 */\n /* 父结点存放到数组的下标为(n + i)的位置 */\n HuffNode[x1].parent = n + i;\n HuffNode[x2].parent = n + i;\n /* 设置 父结点的属性 */\n HuffNode[n + i].weight = HuffNode[x1].weight + HuffNode[x2].weight;\n HuffNode[n + i].lchild = x1;\n HuffNode[n + i].rchild = x2;\n\n printf(\"第 %d 次循环的两个结果的权值为: %d, %d\\n\", i + 1, HuffNode[x1].weight, HuffNode[x2].weight); /* 用于测试 */\n printf(\"\\n\");\n } /* end for */\n /* for(i=0;i>>> 因为“左子树分配0,右字数分配1”\n cd.bit[cd.start] = 1;\n cd.start--; /* 求编码的低一位 */\n\n /* 设置下一循环条件 */\n c = p; //设置c为父结点的下标 准备进行下次while循环,则那时候的“当前的结点”就会变成现在结点的父结点\n p = HuffNode[c].parent; //同理\n\n }/* end while */\n\n /* 保存 求出的每个叶结点的哈夫曼编码和编码的起始位 */\n for(j = cd.start + 1; j < n; j++)\n {\n HuffCode[i].bit[j] = cd.bit[j];\n }\n HuffCode[i].start = cd.start + 1; //编码的起始位\n\n } /* end for */\n /*对哈夫曼树进行编码结束*/\n\n /* 打印已保存好的所有存在编码的哈夫曼编码 */\n for(i = 0; i < n; i++)\n {\n printf(\"第%d位置的树结点的编码为:: \", i);\n for(j = HuffCode[i].start; j < n; j++)//从有效位置开始输出该结点的编码\n {\n printf(\"%d\", HuffCode[i].bit[j]);\n }\n printf(\" 结点的属性bit数组中有效的编码开始位置为:bit[%d]\\n\", HuffCode[i].start);\n }\n /* 打印结束 */\n\n printf(\"Decoding?Please Enter code:\\n\");\n scanf(\"%s\", &pp);\n decodeing(pp, HuffNode, n);\n getch();\n return 0;\n}\n\n\n```\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170908_hfms","published":1,"date":"2020-01-15T05:50:39.632Z","updated":"2021-03-10T13:50:15.257Z","layout":"post","photos":[],"link":"","_id":"ckm3invdz007h24ujorpbzycu","content":"
\n

哈夫曼树:又称最优二叉树,是一种带权路径长度最短的二叉树;
哈夫曼编码:哈夫曼树的一个应用,如JPEG中就应用;

\n
\n
所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度\n(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。\n树的带权路径长度记为WPL = (W1*L1+W2*L2+W3*L3+...+Wn*Ln),\nN个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。\n可以证明哈夫曼树的WPL是最小的。\n\n一般权值用来表示频率大小,频率越大则权值越高\n
\n
\n

参考博客1: http://www.cnblogs.com/junyuhuang/p/4127095.html

\n
\n
\n

参考博客2: http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html

\n
\n
\n

####哈弗曼编码原理(转载)

\n
\n
      哈夫曼编码使用一种特别的方法为信号源中的每个符号设定二进制码。出现频率更大的符号将获得更短的比特,出现频率更小的符号将被分配更长的比特,以此来提高数据压缩率,提高传输效率。具体编码步骤主要为,\n\n      1、统计:\n\n      在开始编码时,通常都需要对信号源,也就是本文的一段文字,进行处理,计算出每个符号出现的频率,得到信号源的基本情况。接下来就是对统计信息进行处理了\n\n      \n\n      2、构造优先对列:\n\n      把得到的符号添加到优先队列中,此优先队列的进出逻辑是频率低的先出,因此在设计优先队列时需要如此设计,如果不熟悉优先队列,请阅读相关书籍,在此不做过多概述。得到包含所有字符的优先队列后,就是处理优先队列中的数据了。\n\n\n      3、构造哈夫曼树:\n\n      哈夫曼树是带权值得二叉树,我们使用的哈夫曼树的权值自然就是符号的频率了,我们构建哈夫曼树是自底向上的,先构建叶子节点,然后逐步向上,最终完成整颗树。先把队列中的一个符号出列,也就是最小频率的符号,,然后再出列一个符号。这两个符号将作为哈夫曼树的节点,而且这两个节点将作为新节点,也就是它们父节点,的左右孩子节点。新节点的频率,即权值,为孩子节点的和。把这个新节点添加到队列中(队列会重新根据权值排序)。重复上面的步骤,两个符号出列,构造新的父节点,入列……直到队列最后只剩下一个节点,这个节点也就是哈夫曼树的根节点了。\n\n      \n\n      4、为哈弗曼树编码:\n\n      哈夫曼树的来自信号源的符号都是叶子节点,需要知道下。树的根节点分配比特0,左子树分配0,右字数分配1。然后就可以得到符号的码值了。\n
\n

####哈夫曼编码步骤

\n
\n

1.首先构建一个元素为哈夫曼树结点的数组用于存储哈夫曼树(线性);

\n
哈夫曼树数据结构包括元素数据、权值、父结点位置(数组下标)、两个子结点位置\n\ntypedef struct\n{\n    int weight;\n    int parent;\n    int lchild;\n    int rchild;\n    int value;\n} HNodeType\n

2.初始化数组,设置树元素的默认属性;然后输入要编码的数据及其权值,存储到数组前几位,这几个即为哈夫曼树的叶子结点;

\n
哈夫曼树中有效的数据仅仅是叶子结点,\n而非叶子结点是为了构建哈夫曼树而加入的\n

3.开始构建哈夫曼树:找出整个数组中有数据且权值最低的两个作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和;
然后把新构建的根结点存放到数中空位置,然后继续从整个数组中寻找。。。直到有效元素全部读取。

\n

4.编码,树的根节点分配比特0,左子树分配0,右字数分配1;

\n

5.解码,思路就是 把要解码的字符串像学中学方程一样代入到哈夫曼树中,按照左子树分配0,右字数分配1的原则,一个一个遍历出来

\n
\n
\n
示例(转载):\n\n  假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,\n那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:\n

  \"图片说明\"

\n
   虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:\n

  \"图片说明\"

\n
再依次建立哈夫曼树,如下图:\n

  \"图片说明\"

\n
其中各个权值替换对应的字符即为下图:\n

  \"图片说明\"

\n
如下图也可以加深大家的理解:\n

  \"图片说明\"

\n
\n
\n
\n

代码实例

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*-------------------------------------------------------------------------
* Name: 哈夫曼编码源代码。
* Date: 2011.04.16
* Author: Jeffrey Hill+Jezze(解码部分)
* 在 Win-TC 下测试通过
* 实现过程:着先通过 HuffmanTree() 函数构造哈夫曼树,然后在主函数 main()中
* 自底向上开始(也就是从数组序号为零的结点开始)向上层层判断,若在
* 父结点左侧,则置码为 0,若在右侧,则置码为 1。最后输出生成的编码。
*------------------------------------------------------------------------*/


//结点个数 = 叶子结点个数 * 2 - 1
#include <stdio.h>
#include<stdlib.h>

#define MAXBIT 100
#define MAXVALUE 10000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2 -1

typedef struct
{
int bit[MAXBIT]; //结点的编码
int start; //结点编码的起始位,有效位置,如当start=2, 则该结点的编码从bit[2]开始
} HCodeType; /* 编码结构体 */

typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
int value;
} HNodeType; /* 结点结构体 */

/* 构造一颗哈夫曼树 */
void HuffmanTree(HNodeType HuffNode[MAXNODE], int n)
{
/* i、j: 循环变量,
m1、m2:构造哈夫曼树不同过程中两个最小权值结点的权值,
x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号。*/
int i, j, m1, m2, x1, x2;
/* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */
for(i = 0; i < 2 * n - 1; i++)
{
HuffNode[i].weight = 0;//权值
HuffNode[i].parent = -1;//父结点位置初始化为-1,表示没有父结点,到时候要通过该属性进行判断,排除掉已加入到树的元素
HuffNode[i].lchild = -1;//初始化左子树根节点位置
HuffNode[i].rchild = -1;//初始化右子树根节点位置
HuffNode[i].value = i; //实际值,可根据情况替换为字母
} /* end for */

/* 用户输入 n 个叶子结点的权值 */
for(i = 0; i < n; i++)
{
printf(\"请输入第 %d 个元素的权值: \\n\", i);
scanf(\"%d\", &HuffNode[i].weight);
} /* end for */

/* 循环构造 Huffman 树 */
for(i = 0; i < n - 1; i++) // 循环叶子结点的个数,一次循环要形成一个“新”的二叉树
{
m1 = m2 = MAXVALUE; //m1、m2中存放两个无父结点且结点权值最小的两个结点
x1 = x2 = 0;
//找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树
for(j = 0; j < n + i; j++) //循环数组中元素个数
{
//要找到当前数组中所有的元素中最小的两位,处理之后又把结果存到数组中所以要(n+i)
if(HuffNode[j].weight < m1 && HuffNode[j].parent == -1)
{
//当前的权值更小,把当前元素赋值给m1,把原来m1的赋值给m2
m2 = m1;
x2 = x1;
m1 = HuffNode[j].weight;
x1 = j;
}
else if(HuffNode[j].weight < m2 && HuffNode[j].parent == -1)
{
//当前的权值大于m1但是小于m2,把其赋值给m2
m2 = HuffNode[j].weight;
x2 = j;
}
} /* end for */
/* 最终,m1每次存放最小权值,m2存放次小的 */

/* 设置 找到的两个子结点 x1、x2 的父结点信息 */
/* 父结点存放到数组的下标为(n + i)的位置 */
HuffNode[x1].parent = n + i;
HuffNode[x2].parent = n + i;
/* 设置 父结点的属性 */
HuffNode[n + i].weight = HuffNode[x1].weight + HuffNode[x2].weight;
HuffNode[n + i].lchild = x1;
HuffNode[n + i].rchild = x2;

printf(\"第 %d 次循环的两个结果的权值为: %d, %d\\n\", i + 1, HuffNode[x1].weight, HuffNode[x2].weight); /* 用于测试 */
printf(\"\\n\");
} /* end for */
/* for(i=0;i<n+2;i++)
{
printf(\" Parents:%d,lchild:%d,rchild:%d,value:%d,weight:%d\\n\",HuffNode[i].parent,HuffNode[i].lchild,HuffNode[i].rchild,HuffNode[i].value,HuffNode[i].weight);
}*///测试
} /* end HuffmanTree */

//解码
void decodeing(char string[], HNodeType Buf[], int Num)
{
//tmp:循环中临时存放哈夫曼树数组的元素下标,从大到小
int i, tmp = 0, code[1024];
int m = 2 * Num - 1;
char *nump;
char num[1024];
int len = strlen(string);

for(i = 0; i < len; i++)
{
if(string[i] == '0')
num[i] = 0;
else
num[i] = 1;
}
i = 0;
nump = &num[0];//nump指向要解码的字符数组的第一个元素的地址

printf(\" 解码结果为: \");
while(nump < (&num[len]))//循环遍历要解码的字符数组(即输入的解码前字符串),直到最后一个数组元素
{
tmp = m - 1;//每次循环设置初始元素下标,设置为 哈夫曼树数组 的最后一个有效数据元素的下标(树的根节点)
while((Buf[tmp].lchild != -1) && (Buf[tmp].rchild != -1))//循环判断该元素有没有子结点,直到没有子结点,则说明遍历到叶子结点,则说明找到一个解码的结果
{
if(*nump == 0) //判断要解码的字符数组的当前元素是否为0 0表示左结点 1表示有结点
{
tmp = Buf[tmp].lchild ;
}
else
{
tmp = Buf[tmp].rchild;
}
nump++; //数组元素是字符,每个元素只占一个字节,所以++也就是地址加一,指向数组下一个元素
}

printf(\" %d\", nump, &num[strlen(string)], Buf[tmp].value);
}
}


int main(void)
{

HNodeType HuffNode[MAXNODE]; /* 定义一个结点结构体数组 */
HCodeType HuffCode[MAXLEAF], cd; /* 定义一个编码结构体数组, 同时定义一个临时变量来存放求解编码时的信息(即cd.bit存放当前结点的编码的倒序,cd.start存放结点编码开始位置) */
/*
i、j: 循环变量,
c 循环体中当前的结点在数组中的下标
p 当前结点的父结点在数组中的下标
n 有效数据的个数(叶子结点的个数)
*/
int i, j, c, p, n;
char pp[100];
printf(\"Please input n:\\n\");
scanf(\"%d\", &n);
HuffmanTree(HuffNode, n);//生成哈夫曼树

/*对哈夫曼树进行编码*/
for(i = 0; i < n; i++)//循环所有的有效数据,一个一个进行编码 (0 ~ n-1)
{
cd.start = n - 1; //
c = i; //c 当前结点在数组中的下标 整体上来说可以表示循环在“树”中走过的结点下标
p = HuffNode[c].parent; //p 当前结点的父结点在数组中的下标
while(p != -1) /* 判断父结点存在 */
{
if(HuffNode[p].lchild == c) //判断当前结点是不是父结点的左子树根节点 如果是的话就 赋值0
cd.bit[cd.start] = 0;
else //如果不是的话就 赋值1 >>>> 因为“左子树分配0,右字数分配1”
cd.bit[cd.start] = 1;
cd.start--; /* 求编码的低一位 */

/* 设置下一循环条件 */
c = p; //设置c为父结点的下标 准备进行下次while循环,则那时候的“当前的结点”就会变成现在结点的父结点
p = HuffNode[c].parent; //同理

}/* end while */

/* 保存 求出的每个叶结点的哈夫曼编码和编码的起始位 */
for(j = cd.start + 1; j < n; j++)
{
HuffCode[i].bit[j] = cd.bit[j];
}
HuffCode[i].start = cd.start + 1; //编码的起始位

} /* end for */
/*对哈夫曼树进行编码结束*/

/* 打印已保存好的所有存在编码的哈夫曼编码 */
for(i = 0; i < n; i++)
{
printf(\"第%d位置的树结点的编码为:: \", i);
for(j = HuffCode[i].start; j < n; j++)//从有效位置开始输出该结点的编码
{
printf(\"%d\", HuffCode[i].bit[j]);
}
printf(\" 结点的属性bit数组中有效的编码开始位置为:bit[%d]\\n\", HuffCode[i].start);
}
/* 打印结束 */

printf(\"Decoding?Please Enter code:\\n\");
scanf(\"%s\", &pp);
decodeing(pp, HuffNode, n);
getch();
return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

哈夫曼树:又称最优二叉树,是一种带权路径长度最短的二叉树;
哈夫曼编码:哈夫曼树的一个应用,如JPEG中就应用;

\n
\n
所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度\n(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。\n树的带权路径长度记为WPL = (W1*L1+W2*L2+W3*L3+...+Wn*Ln),\nN个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。\n可以证明哈夫曼树的WPL是最小的。\n\n一般权值用来表示频率大小,频率越大则权值越高\n
","more":"
\n

参考博客1: http://www.cnblogs.com/junyuhuang/p/4127095.html

\n
\n
\n

参考博客2: http://www.cnblogs.com/Jezze/archive/2011/12/23/2299884.html

\n
\n
\n

####哈弗曼编码原理(转载)

\n
\n
      哈夫曼编码使用一种特别的方法为信号源中的每个符号设定二进制码。出现频率更大的符号将获得更短的比特,出现频率更小的符号将被分配更长的比特,以此来提高数据压缩率,提高传输效率。具体编码步骤主要为,\n\n      1、统计:\n\n      在开始编码时,通常都需要对信号源,也就是本文的一段文字,进行处理,计算出每个符号出现的频率,得到信号源的基本情况。接下来就是对统计信息进行处理了\n\n      \n\n      2、构造优先对列:\n\n      把得到的符号添加到优先队列中,此优先队列的进出逻辑是频率低的先出,因此在设计优先队列时需要如此设计,如果不熟悉优先队列,请阅读相关书籍,在此不做过多概述。得到包含所有字符的优先队列后,就是处理优先队列中的数据了。\n\n\n      3、构造哈夫曼树:\n\n      哈夫曼树是带权值得二叉树,我们使用的哈夫曼树的权值自然就是符号的频率了,我们构建哈夫曼树是自底向上的,先构建叶子节点,然后逐步向上,最终完成整颗树。先把队列中的一个符号出列,也就是最小频率的符号,,然后再出列一个符号。这两个符号将作为哈夫曼树的节点,而且这两个节点将作为新节点,也就是它们父节点,的左右孩子节点。新节点的频率,即权值,为孩子节点的和。把这个新节点添加到队列中(队列会重新根据权值排序)。重复上面的步骤,两个符号出列,构造新的父节点,入列……直到队列最后只剩下一个节点,这个节点也就是哈夫曼树的根节点了。\n\n      \n\n      4、为哈弗曼树编码:\n\n      哈夫曼树的来自信号源的符号都是叶子节点,需要知道下。树的根节点分配比特0,左子树分配0,右字数分配1。然后就可以得到符号的码值了。\n
\n

####哈夫曼编码步骤

\n
\n

1.首先构建一个元素为哈夫曼树结点的数组用于存储哈夫曼树(线性);

\n
哈夫曼树数据结构包括元素数据、权值、父结点位置(数组下标)、两个子结点位置\n\ntypedef struct\n{\n    int weight;\n    int parent;\n    int lchild;\n    int rchild;\n    int value;\n} HNodeType\n

2.初始化数组,设置树元素的默认属性;然后输入要编码的数据及其权值,存储到数组前几位,这几个即为哈夫曼树的叶子结点;

\n
哈夫曼树中有效的数据仅仅是叶子结点,\n而非叶子结点是为了构建哈夫曼树而加入的\n

3.开始构建哈夫曼树:找出整个数组中有数据且权值最低的两个作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和;
然后把新构建的根结点存放到数中空位置,然后继续从整个数组中寻找。。。直到有效元素全部读取。

\n

4.编码,树的根节点分配比特0,左子树分配0,右字数分配1;

\n

5.解码,思路就是 把要解码的字符串像学中学方程一样代入到哈夫曼树中,按照左子树分配0,右字数分配1的原则,一个一个遍历出来

\n
\n
\n
示例(转载):\n\n  假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,\n那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:\n

  \"图片说明\"

\n
   虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:\n

  \"图片说明\"

\n
再依次建立哈夫曼树,如下图:\n

  \"图片说明\"

\n
其中各个权值替换对应的字符即为下图:\n

  \"图片说明\"

\n
如下图也可以加深大家的理解:\n

  \"图片说明\"

\n
\n
\n
\n

代码实例

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*-------------------------------------------------------------------------
* Name: 哈夫曼编码源代码。
* Date: 2011.04.16
* Author: Jeffrey Hill+Jezze(解码部分)
* 在 Win-TC 下测试通过
* 实现过程:着先通过 HuffmanTree() 函数构造哈夫曼树,然后在主函数 main()中
* 自底向上开始(也就是从数组序号为零的结点开始)向上层层判断,若在
* 父结点左侧,则置码为 0,若在右侧,则置码为 1。最后输出生成的编码。
*------------------------------------------------------------------------*/


//结点个数 = 叶子结点个数 * 2 - 1
#include <stdio.h>
#include<stdlib.h>

#define MAXBIT 100
#define MAXVALUE 10000
#define MAXLEAF 30
#define MAXNODE MAXLEAF*2 -1

typedef struct
{
int bit[MAXBIT]; //结点的编码
int start; //结点编码的起始位,有效位置,如当start=2, 则该结点的编码从bit[2]开始
} HCodeType; /* 编码结构体 */

typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
int value;
} HNodeType; /* 结点结构体 */

/* 构造一颗哈夫曼树 */
void HuffmanTree(HNodeType HuffNode[MAXNODE], int n)
{
/* i、j: 循环变量,
m1、m2:构造哈夫曼树不同过程中两个最小权值结点的权值,
x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号。*/
int i, j, m1, m2, x1, x2;
/* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */
for(i = 0; i < 2 * n - 1; i++)
{
HuffNode[i].weight = 0;//权值
HuffNode[i].parent = -1;//父结点位置初始化为-1,表示没有父结点,到时候要通过该属性进行判断,排除掉已加入到树的元素
HuffNode[i].lchild = -1;//初始化左子树根节点位置
HuffNode[i].rchild = -1;//初始化右子树根节点位置
HuffNode[i].value = i; //实际值,可根据情况替换为字母
} /* end for */

/* 用户输入 n 个叶子结点的权值 */
for(i = 0; i < n; i++)
{
printf(\"请输入第 %d 个元素的权值: \\n\", i);
scanf(\"%d\", &HuffNode[i].weight);
} /* end for */

/* 循环构造 Huffman 树 */
for(i = 0; i < n - 1; i++) // 循环叶子结点的个数,一次循环要形成一个“新”的二叉树
{
m1 = m2 = MAXVALUE; //m1、m2中存放两个无父结点且结点权值最小的两个结点
x1 = x2 = 0;
//找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树
for(j = 0; j < n + i; j++) //循环数组中元素个数
{
//要找到当前数组中所有的元素中最小的两位,处理之后又把结果存到数组中所以要(n+i)
if(HuffNode[j].weight < m1 && HuffNode[j].parent == -1)
{
//当前的权值更小,把当前元素赋值给m1,把原来m1的赋值给m2
m2 = m1;
x2 = x1;
m1 = HuffNode[j].weight;
x1 = j;
}
else if(HuffNode[j].weight < m2 && HuffNode[j].parent == -1)
{
//当前的权值大于m1但是小于m2,把其赋值给m2
m2 = HuffNode[j].weight;
x2 = j;
}
} /* end for */
/* 最终,m1每次存放最小权值,m2存放次小的 */

/* 设置 找到的两个子结点 x1、x2 的父结点信息 */
/* 父结点存放到数组的下标为(n + i)的位置 */
HuffNode[x1].parent = n + i;
HuffNode[x2].parent = n + i;
/* 设置 父结点的属性 */
HuffNode[n + i].weight = HuffNode[x1].weight + HuffNode[x2].weight;
HuffNode[n + i].lchild = x1;
HuffNode[n + i].rchild = x2;

printf(\"第 %d 次循环的两个结果的权值为: %d, %d\\n\", i + 1, HuffNode[x1].weight, HuffNode[x2].weight); /* 用于测试 */
printf(\"\\n\");
} /* end for */
/* for(i=0;i<n+2;i++)
{
printf(\" Parents:%d,lchild:%d,rchild:%d,value:%d,weight:%d\\n\",HuffNode[i].parent,HuffNode[i].lchild,HuffNode[i].rchild,HuffNode[i].value,HuffNode[i].weight);
}*///测试
} /* end HuffmanTree */

//解码
void decodeing(char string[], HNodeType Buf[], int Num)
{
//tmp:循环中临时存放哈夫曼树数组的元素下标,从大到小
int i, tmp = 0, code[1024];
int m = 2 * Num - 1;
char *nump;
char num[1024];
int len = strlen(string);

for(i = 0; i < len; i++)
{
if(string[i] == '0')
num[i] = 0;
else
num[i] = 1;
}
i = 0;
nump = &num[0];//nump指向要解码的字符数组的第一个元素的地址

printf(\" 解码结果为: \");
while(nump < (&num[len]))//循环遍历要解码的字符数组(即输入的解码前字符串),直到最后一个数组元素
{
tmp = m - 1;//每次循环设置初始元素下标,设置为 哈夫曼树数组 的最后一个有效数据元素的下标(树的根节点)
while((Buf[tmp].lchild != -1) && (Buf[tmp].rchild != -1))//循环判断该元素有没有子结点,直到没有子结点,则说明遍历到叶子结点,则说明找到一个解码的结果
{
if(*nump == 0) //判断要解码的字符数组的当前元素是否为0 0表示左结点 1表示有结点
{
tmp = Buf[tmp].lchild ;
}
else
{
tmp = Buf[tmp].rchild;
}
nump++; //数组元素是字符,每个元素只占一个字节,所以++也就是地址加一,指向数组下一个元素
}

printf(\" %d\", nump, &num[strlen(string)], Buf[tmp].value);
}
}


int main(void)
{

HNodeType HuffNode[MAXNODE]; /* 定义一个结点结构体数组 */
HCodeType HuffCode[MAXLEAF], cd; /* 定义一个编码结构体数组, 同时定义一个临时变量来存放求解编码时的信息(即cd.bit存放当前结点的编码的倒序,cd.start存放结点编码开始位置) */
/*
i、j: 循环变量,
c 循环体中当前的结点在数组中的下标
p 当前结点的父结点在数组中的下标
n 有效数据的个数(叶子结点的个数)
*/
int i, j, c, p, n;
char pp[100];
printf(\"Please input n:\\n\");
scanf(\"%d\", &n);
HuffmanTree(HuffNode, n);//生成哈夫曼树

/*对哈夫曼树进行编码*/
for(i = 0; i < n; i++)//循环所有的有效数据,一个一个进行编码 (0 ~ n-1)
{
cd.start = n - 1; //
c = i; //c 当前结点在数组中的下标 整体上来说可以表示循环在“树”中走过的结点下标
p = HuffNode[c].parent; //p 当前结点的父结点在数组中的下标
while(p != -1) /* 判断父结点存在 */
{
if(HuffNode[p].lchild == c) //判断当前结点是不是父结点的左子树根节点 如果是的话就 赋值0
cd.bit[cd.start] = 0;
else //如果不是的话就 赋值1 >>>> 因为“左子树分配0,右字数分配1”
cd.bit[cd.start] = 1;
cd.start--; /* 求编码的低一位 */

/* 设置下一循环条件 */
c = p; //设置c为父结点的下标 准备进行下次while循环,则那时候的“当前的结点”就会变成现在结点的父结点
p = HuffNode[c].parent; //同理

}/* end while */

/* 保存 求出的每个叶结点的哈夫曼编码和编码的起始位 */
for(j = cd.start + 1; j < n; j++)
{
HuffCode[i].bit[j] = cd.bit[j];
}
HuffCode[i].start = cd.start + 1; //编码的起始位

} /* end for */
/*对哈夫曼树进行编码结束*/

/* 打印已保存好的所有存在编码的哈夫曼编码 */
for(i = 0; i < n; i++)
{
printf(\"第%d位置的树结点的编码为:: \", i);
for(j = HuffCode[i].start; j < n; j++)//从有效位置开始输出该结点的编码
{
printf(\"%d\", HuffCode[i].bit[j]);
}
printf(\" 结点的属性bit数组中有效的编码开始位置为:bit[%d]\\n\", HuffCode[i].start);
}
/* 打印结束 */

printf(\"Decoding?Please Enter code:\\n\");
scanf(\"%s\", &pp);
decodeing(pp, HuffNode, n);
getch();
return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"图","comments":1,"description":null,"_content":"\n> ##图: 图是由顶点集(VertexSet)和边集(EdgeSet)组成,针对图G,顶点集和边集分别记为V(G)和E(G)。依据图的边集是否为有向,可把图分为有向图和无向图,根据图是否有权重,可以分为有权图和无权图。\n\n**线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,而图结构中的元素则是“多对多”的关系**\n\n 顶点(Vertex)、弧(Arc)、弧头(初始点)、弧尾(终结点)、边(Edge)、有向图(Directed graph)、\n 无向图(Undigraph)、完全图(Completed grapg)、有向完全图、稀疏图(Sparse graph)、\n 稠密图(Dense graph)、权(weigh)、网(network)、无向网、有向网、子图(Subgraph)、\n 邻接点(Adjacent)、度(Degree)、入度(Indegree)、出度(Outdegree)、路径(path)、\n 回路(环)、简单路径、简单回路(简单环)、连通、连通图(Connected graph)、连通分量(Connected Component)、\n 强连通图、强连通分量(有向图中的极大强连通子图)、生成树、极小连通子图、有向树。\n\n\n\n> ##图的存储: \n\n1. 邻接矩阵:用一个二维数组表示图中顶点和顶点,边的关系;形成的矩阵中可以自定义边的权值表示, 例如:0表示没有边, 其他大于0的数n表示两个顶点有边且权值为n\n\n  {% qnimg map.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n  {% qnimg map2.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n2. 邻接表\n\n \n 接矩阵与邻接表相比,它会造成空间的一定损失,它需要为每个顶点都分配n个边的空间,\n 其实有很多边都是不存在边,但是邻接表的实现就不一样,它只关心存在的边,不关心不存在的边。\n \n\n  {% qnimg map3.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n> ##图的遍历:\n \n1.广度优先\n\n 1.从isTrav数组中选择一个未被访问的邻接点,标记为已访问\n 2.依次访问Vi的所有未被访问的邻接点,并标记为已被访问\n 3.从邻接点出发进行广度优先遍历,直到图中所有和Vi有路径相通的顶点都被访问\n 4.重复1-3的步骤直到所有的顶点都被访问\n \n2.深度优先\n \n 1.从isTrav数组中选择一个未被访问的邻接点Vi,标记为已访问\n 2.从Vi邻接点出发进行深度优先遍历\n 3.重复2步骤,直到所有的和Vi有路径相通的顶点都被访问过\n 4.重复1-3操作,直到所有顶点都被访问过\n\n> ##代码演示\n\n```c\n\n//邻接矩阵\n #include \n #include \n \n #define VERTEX_MAX 26\n #define MAXVALUE 32767 //表示当权值为MAXVALUE时,两个顶点没有相连\n \n typedef struct\n {\n char Vertex[VERTEX_MAX]; //保存顶点信息(序号或字母)\n int Edges[VERTEX_MAX][VERTEX_MAX]; //保存边的权\n int isTrav[VERTEX_MAX]; //遍历标志\n int VertexNum; //顶点数量\n int EdgeNum; //边数量\n int GraphType; //图的类型(0无向图 1有向图)\n } MatrixGraph; //定义邻接矩阵图结构\n \n void CreateMatrixGraph(MatrixGraph *G); //创建邻接矩阵图\n void OutMatrix(MatrixGraph *G); //输出邻接矩阵\n \n void CreateMatrixGraph(MatrixGraph *G)\n {\n int i, j, k, weight;\n char start, end; //边的起始顶点\n \n printf(\"输入各顶点的信息:\\n\"); //输入顶点\n for(i=0; iVertexNum; i++)\n {\n fflush(stdin);\n printf(\"第%d个顶点信息:\", i+1);\n scanf(\"%c\", &(G->Vertex[i]));\n }\n \n printf(\"输入构成各边的两个顶点以及权值:\\n\");\n \n for(k=0; kEdgeNum; k++)\n {\n printf(\"第%d条边:\", k+1);\n fflush(stdin);\n scanf(\"%c %c %d\", &start, &end, &weight);\n for(i=0; start!=G->Vertex[i]; i++); //查找已有的顶点中是否包含当前的\"起始顶点\"\n for(j=0; end!=G->Vertex[j]; j++); //同上\n \n // printf(\"%d %d\", i, j);\n G->Edges[i][j] = weight; //对应位置保存权值,表示有一条边\n if(G->GraphType == 0) //判断是不是无向图\n {\n G->Edges[j][i] = weight; //在对角位置保存权值\n }\n }\n }\n \n void OutMatrix(MatrixGraph *G)\n {\n int i, j;\n \n printf(\" \");\n for(j=0; jVertexNum; j++)\n printf(\"\\t%c\", G->Vertex[j]); //输出顶点信息\n printf(\"\\n\");\n \n for(i=0; iVertexNum; i++)\n {\n printf(\"%c\", G->Vertex[i]);\n for(j=0; jVertexNum; j++)\n {\n if(G->Edges[i][j] >= MAXVALUE) //如果权值为最大值\n printf(\"\\t #\"); //输出无穷大符号\n else\n printf(\"\\t%d\", G->Edges[i][j]); //输出边的权值\n }\n \n printf(\"\\n\");\n }\n }\n \n int main()\n {\n MatrixGraph G;\n int i, j;\n printf(\"输入生成图的类型 (0无向图 1有向图)\");\n scanf(\"%d\", &G.GraphType);\n printf(\"输入图的顶点数量和边数量\");\n scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum); //输入图顶点数和边数\n for(i=0; i\n #include \n \n #define VERTEX_MAX 26\n \n typedef struct edgeNode\n {\n int Vertex; //顶点信息\n int weight; //权值\n struct edgeNode *next; //下一个顶点地址指针\n }EdgeNode; //边的结构\n \n typedef struct\n {\n EdgeNode* AdjList[VERTEX_MAX]; //顶点指针\n int VextexNum, EdgeNum; //顶点数量,边数量\n int GraphType; //图的类型(0无向图 1有向图)\n }ListGraph; //图的结构\n \n void CreateGraph(ListGraph *G);\n void OutList(ListGraph *G);\n \n void CreateGraph(ListGraph *G)\n {\n int i, weight;\n int start, end;\n EdgeNode *s;\n for(i=0; i<=G->VextexNum; i++) //初始化\n G->AdjList[i] = NULL;\n \n printf(\"输入构成各边的两个顶点以及权值:\\n\");\n \n for(i=0; iEdgeNum; i++)\n {\n printf(\"第%d条边 开始顶点 结束顶点 权值\", i+1);\n fflush(stdin);\n scanf(\"%d %d %d\", &start, &end, &weight);\n s = (EdgeNode *)malloc(sizeof(EdgeNode));\n s->next = G->AdjList[start];\n s->Vertex = end;\n s->weight = weight;\n G->AdjList[start] = s;//把生成的边信息赋值给图\n \n if(G->GraphType == 0) //判断是不是有向图\n {\n s = (EdgeNode *)malloc(sizeof(EdgeNode));\n s->next = G->AdjList[end];\n s->Vertex = start;\n s->weight = weight;\n G->AdjList[end] = s;//把生成的边信息赋值给图\n }\n }\n }\n \n void OutList(ListGraph *G)\n {\n int i;\n EdgeNode *s;\n for(i=0; i<=G->VextexNum; i++)\n {\n printf(\"顶点%d\", i);\n s = G->AdjList[i];\n while(s)\n {\n printf(\"->%d(%d)\", s->Vertex, s->weight);\n s = s->next;\n }\n printf(\"\\n\");\n }\n }\n \n int main()\n {\n ListGraph G;\n printf(\"输入生成图的类型 (0无向图 1有向图)\");\n scanf(\"%d\", &G.GraphType);\n printf(\"输入图的顶点数量和边数量\");\n scanf(\"%d %d\", &G.VextexNum, &G.EdgeNum);\n \n CreateGraph(&G);\n OutList(&G);\n \n return 0;\n }\n \n```\n\n---\n---\n\n```c\n\n//深度优先和广度优先\n #include \n #include \n #include \"AdjMatrixGraph.h\"\n \n #define QUEUE_MAXSIZE 30 //队列的最大容量\n \n typedef struct\n {\n int Data[QUEUE_MAXSIZE];//数据域\n int head; //队头指针\n int tail; //队尾指针\n }SeqQueue; //队列结构\n \n //队列操作函数\n void QueueInit(SeqQueue *q); //初始化一个队列\n int QueueIsEmpty(SeqQueue q); //判断队列是否为空\n int QueueIn(SeqQueue *q, int n); //入队\n int QueueOut(SeqQueue *q, int *ch); //出队\n \n //图操作函数\n void DFSTraverse(MatrixGraph *G); //深度优先遍历\n void BFSTraverse(MatrixGraph *G); //广度优先遍历\n void DFSM(MatrixGraph *G, int i);\n void BFSM(MatrixGraph *G, int i);\n \n void QueueInit(SeqQueue *q){\n q->head = q->tail = 0;\n }\n \n int QueueIsEmpty(SeqQueue q)\n {\n return q.head = q.tail;\n }\n \n int QueueIn(SeqQueue *q, int n)\n {\n if((q->tail + 1) % q->head != 0)\n {\n q->Data[q->tail] = n;\n q->tail = (q->tail + 1) % QUEUE_MAXSIZE;\n return 1;\n }\n else\n {\n return 0;\n }\n }\n \n int QueueOut(SeqQueue *q, int *ch)\n {\n if(q->head != q->tail)\n {\n *ch = q->Data[q->head];\n q->head = (q->head + 1) % QUEUE_MAXSIZE;\n return 1;\n }\n else\n {\n return 0;\n }\n }\n \n void DFSTraverse(MatrixGraph *G)\n {\n int i;\n for(i=0; iVertexNum; i++)\n {\n G->isTrav[i] = 0;\n }\n \n printf(\"深度优先遍历\");\n for(i=0; iVertexNum; i++)\n {\n if(!G->isTrav[i])\n {\n DFSM(G, i);\n }\n }\n }\n \n void BFSTraverse(MatrixGraph *G)\n {\n int i;\n for(i=0; iVertexNum; i++)\n {\n G->isTrav[i] = 0;\n }\n \n printf(\"广度优先遍历\");\n for(i=0; iVertexNum; i++)\n {\n if(!G->isTrav[i])\n {\n BFSM(G, i);\n }\n }\n }\n \n void DFSM(MatrixGraph *G, int i)\n {\n int j;\n G->isTrav[i] = i;\n printf(\"->%c\", G->Vertex[i]);\n \n for(j=0; jVertexNum; j++)\n {\n if(G->Edges[i][j] != MAXVALUE && !G->isTrav[i])\n {\n DFSM(G, j);\n }\n }\n }\n \n void BFSM(MatrixGraph *G, int k)\n {\n int i, j;\n SeqQueue Q;\n QueueInit(&Q);\n \n G->isTrav[k] = 1;\n printf(\"->%c\", G->Vertex[k]);\n \n QueueIn(&Q, k);\n while(!QueueIsEmpty(Q))\n {\n QueueOut(&Q, &i);\n for(j=0; jVertexNum; j++)\n {\n if(G->Edges[i][j] != MAXVALUE && !G->isTrav[j])\n {\n printf(\"->%c\", G->Vertex[j]);\n G->isTrav[j] = 1;\n QueueIn(&Q, j);\n }\n }\n }\n }\n \n int main()\n {\n MatrixGraph G;\n int i, j;\n char select;\n do\n {\n printf(\"输入生成图的类型: (0:无向图, 1:有向图)\");\n fflush(stdin);\n scanf(\"%d\", &G.GraphType);\n printf(\"输入图的顶点数量和边数量\");\n fflush(stdin);\n scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum);\n for(i=0; i 个人博客 欢迎来访: http://zj2626.github.io\n","source":"_posts/20170922_map.md","raw":"---\ntitle: 图\n\ncomments: true \n\ntags: \n - C语言\n\ncategories: \n - 数据结构和算法\n\ndescription: \n\n---\n\n> ##图: 图是由顶点集(VertexSet)和边集(EdgeSet)组成,针对图G,顶点集和边集分别记为V(G)和E(G)。依据图的边集是否为有向,可把图分为有向图和无向图,根据图是否有权重,可以分为有权图和无权图。\n\n**线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,而图结构中的元素则是“多对多”的关系**\n\n 顶点(Vertex)、弧(Arc)、弧头(初始点)、弧尾(终结点)、边(Edge)、有向图(Directed graph)、\n 无向图(Undigraph)、完全图(Completed grapg)、有向完全图、稀疏图(Sparse graph)、\n 稠密图(Dense graph)、权(weigh)、网(network)、无向网、有向网、子图(Subgraph)、\n 邻接点(Adjacent)、度(Degree)、入度(Indegree)、出度(Outdegree)、路径(path)、\n 回路(环)、简单路径、简单回路(简单环)、连通、连通图(Connected graph)、连通分量(Connected Component)、\n 强连通图、强连通分量(有向图中的极大强连通子图)、生成树、极小连通子图、有向树。\n\n\n\n> ##图的存储: \n\n1. 邻接矩阵:用一个二维数组表示图中顶点和顶点,边的关系;形成的矩阵中可以自定义边的权值表示, 例如:0表示没有边, 其他大于0的数n表示两个顶点有边且权值为n\n\n  {% qnimg map.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n  {% qnimg map2.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n2. 邻接表\n\n \n 接矩阵与邻接表相比,它会造成空间的一定损失,它需要为每个顶点都分配n个边的空间,\n 其实有很多边都是不存在边,但是邻接表的实现就不一样,它只关心存在的边,不关心不存在的边。\n \n\n  {% qnimg map3.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n> ##图的遍历:\n \n1.广度优先\n\n 1.从isTrav数组中选择一个未被访问的邻接点,标记为已访问\n 2.依次访问Vi的所有未被访问的邻接点,并标记为已被访问\n 3.从邻接点出发进行广度优先遍历,直到图中所有和Vi有路径相通的顶点都被访问\n 4.重复1-3的步骤直到所有的顶点都被访问\n \n2.深度优先\n \n 1.从isTrav数组中选择一个未被访问的邻接点Vi,标记为已访问\n 2.从Vi邻接点出发进行深度优先遍历\n 3.重复2步骤,直到所有的和Vi有路径相通的顶点都被访问过\n 4.重复1-3操作,直到所有顶点都被访问过\n\n> ##代码演示\n\n```c\n\n//邻接矩阵\n #include \n #include \n \n #define VERTEX_MAX 26\n #define MAXVALUE 32767 //表示当权值为MAXVALUE时,两个顶点没有相连\n \n typedef struct\n {\n char Vertex[VERTEX_MAX]; //保存顶点信息(序号或字母)\n int Edges[VERTEX_MAX][VERTEX_MAX]; //保存边的权\n int isTrav[VERTEX_MAX]; //遍历标志\n int VertexNum; //顶点数量\n int EdgeNum; //边数量\n int GraphType; //图的类型(0无向图 1有向图)\n } MatrixGraph; //定义邻接矩阵图结构\n \n void CreateMatrixGraph(MatrixGraph *G); //创建邻接矩阵图\n void OutMatrix(MatrixGraph *G); //输出邻接矩阵\n \n void CreateMatrixGraph(MatrixGraph *G)\n {\n int i, j, k, weight;\n char start, end; //边的起始顶点\n \n printf(\"输入各顶点的信息:\\n\"); //输入顶点\n for(i=0; iVertexNum; i++)\n {\n fflush(stdin);\n printf(\"第%d个顶点信息:\", i+1);\n scanf(\"%c\", &(G->Vertex[i]));\n }\n \n printf(\"输入构成各边的两个顶点以及权值:\\n\");\n \n for(k=0; kEdgeNum; k++)\n {\n printf(\"第%d条边:\", k+1);\n fflush(stdin);\n scanf(\"%c %c %d\", &start, &end, &weight);\n for(i=0; start!=G->Vertex[i]; i++); //查找已有的顶点中是否包含当前的\"起始顶点\"\n for(j=0; end!=G->Vertex[j]; j++); //同上\n \n // printf(\"%d %d\", i, j);\n G->Edges[i][j] = weight; //对应位置保存权值,表示有一条边\n if(G->GraphType == 0) //判断是不是无向图\n {\n G->Edges[j][i] = weight; //在对角位置保存权值\n }\n }\n }\n \n void OutMatrix(MatrixGraph *G)\n {\n int i, j;\n \n printf(\" \");\n for(j=0; jVertexNum; j++)\n printf(\"\\t%c\", G->Vertex[j]); //输出顶点信息\n printf(\"\\n\");\n \n for(i=0; iVertexNum; i++)\n {\n printf(\"%c\", G->Vertex[i]);\n for(j=0; jVertexNum; j++)\n {\n if(G->Edges[i][j] >= MAXVALUE) //如果权值为最大值\n printf(\"\\t #\"); //输出无穷大符号\n else\n printf(\"\\t%d\", G->Edges[i][j]); //输出边的权值\n }\n \n printf(\"\\n\");\n }\n }\n \n int main()\n {\n MatrixGraph G;\n int i, j;\n printf(\"输入生成图的类型 (0无向图 1有向图)\");\n scanf(\"%d\", &G.GraphType);\n printf(\"输入图的顶点数量和边数量\");\n scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum); //输入图顶点数和边数\n for(i=0; i\n #include \n \n #define VERTEX_MAX 26\n \n typedef struct edgeNode\n {\n int Vertex; //顶点信息\n int weight; //权值\n struct edgeNode *next; //下一个顶点地址指针\n }EdgeNode; //边的结构\n \n typedef struct\n {\n EdgeNode* AdjList[VERTEX_MAX]; //顶点指针\n int VextexNum, EdgeNum; //顶点数量,边数量\n int GraphType; //图的类型(0无向图 1有向图)\n }ListGraph; //图的结构\n \n void CreateGraph(ListGraph *G);\n void OutList(ListGraph *G);\n \n void CreateGraph(ListGraph *G)\n {\n int i, weight;\n int start, end;\n EdgeNode *s;\n for(i=0; i<=G->VextexNum; i++) //初始化\n G->AdjList[i] = NULL;\n \n printf(\"输入构成各边的两个顶点以及权值:\\n\");\n \n for(i=0; iEdgeNum; i++)\n {\n printf(\"第%d条边 开始顶点 结束顶点 权值\", i+1);\n fflush(stdin);\n scanf(\"%d %d %d\", &start, &end, &weight);\n s = (EdgeNode *)malloc(sizeof(EdgeNode));\n s->next = G->AdjList[start];\n s->Vertex = end;\n s->weight = weight;\n G->AdjList[start] = s;//把生成的边信息赋值给图\n \n if(G->GraphType == 0) //判断是不是有向图\n {\n s = (EdgeNode *)malloc(sizeof(EdgeNode));\n s->next = G->AdjList[end];\n s->Vertex = start;\n s->weight = weight;\n G->AdjList[end] = s;//把生成的边信息赋值给图\n }\n }\n }\n \n void OutList(ListGraph *G)\n {\n int i;\n EdgeNode *s;\n for(i=0; i<=G->VextexNum; i++)\n {\n printf(\"顶点%d\", i);\n s = G->AdjList[i];\n while(s)\n {\n printf(\"->%d(%d)\", s->Vertex, s->weight);\n s = s->next;\n }\n printf(\"\\n\");\n }\n }\n \n int main()\n {\n ListGraph G;\n printf(\"输入生成图的类型 (0无向图 1有向图)\");\n scanf(\"%d\", &G.GraphType);\n printf(\"输入图的顶点数量和边数量\");\n scanf(\"%d %d\", &G.VextexNum, &G.EdgeNum);\n \n CreateGraph(&G);\n OutList(&G);\n \n return 0;\n }\n \n```\n\n---\n---\n\n```c\n\n//深度优先和广度优先\n #include \n #include \n #include \"AdjMatrixGraph.h\"\n \n #define QUEUE_MAXSIZE 30 //队列的最大容量\n \n typedef struct\n {\n int Data[QUEUE_MAXSIZE];//数据域\n int head; //队头指针\n int tail; //队尾指针\n }SeqQueue; //队列结构\n \n //队列操作函数\n void QueueInit(SeqQueue *q); //初始化一个队列\n int QueueIsEmpty(SeqQueue q); //判断队列是否为空\n int QueueIn(SeqQueue *q, int n); //入队\n int QueueOut(SeqQueue *q, int *ch); //出队\n \n //图操作函数\n void DFSTraverse(MatrixGraph *G); //深度优先遍历\n void BFSTraverse(MatrixGraph *G); //广度优先遍历\n void DFSM(MatrixGraph *G, int i);\n void BFSM(MatrixGraph *G, int i);\n \n void QueueInit(SeqQueue *q){\n q->head = q->tail = 0;\n }\n \n int QueueIsEmpty(SeqQueue q)\n {\n return q.head = q.tail;\n }\n \n int QueueIn(SeqQueue *q, int n)\n {\n if((q->tail + 1) % q->head != 0)\n {\n q->Data[q->tail] = n;\n q->tail = (q->tail + 1) % QUEUE_MAXSIZE;\n return 1;\n }\n else\n {\n return 0;\n }\n }\n \n int QueueOut(SeqQueue *q, int *ch)\n {\n if(q->head != q->tail)\n {\n *ch = q->Data[q->head];\n q->head = (q->head + 1) % QUEUE_MAXSIZE;\n return 1;\n }\n else\n {\n return 0;\n }\n }\n \n void DFSTraverse(MatrixGraph *G)\n {\n int i;\n for(i=0; iVertexNum; i++)\n {\n G->isTrav[i] = 0;\n }\n \n printf(\"深度优先遍历\");\n for(i=0; iVertexNum; i++)\n {\n if(!G->isTrav[i])\n {\n DFSM(G, i);\n }\n }\n }\n \n void BFSTraverse(MatrixGraph *G)\n {\n int i;\n for(i=0; iVertexNum; i++)\n {\n G->isTrav[i] = 0;\n }\n \n printf(\"广度优先遍历\");\n for(i=0; iVertexNum; i++)\n {\n if(!G->isTrav[i])\n {\n BFSM(G, i);\n }\n }\n }\n \n void DFSM(MatrixGraph *G, int i)\n {\n int j;\n G->isTrav[i] = i;\n printf(\"->%c\", G->Vertex[i]);\n \n for(j=0; jVertexNum; j++)\n {\n if(G->Edges[i][j] != MAXVALUE && !G->isTrav[i])\n {\n DFSM(G, j);\n }\n }\n }\n \n void BFSM(MatrixGraph *G, int k)\n {\n int i, j;\n SeqQueue Q;\n QueueInit(&Q);\n \n G->isTrav[k] = 1;\n printf(\"->%c\", G->Vertex[k]);\n \n QueueIn(&Q, k);\n while(!QueueIsEmpty(Q))\n {\n QueueOut(&Q, &i);\n for(j=0; jVertexNum; j++)\n {\n if(G->Edges[i][j] != MAXVALUE && !G->isTrav[j])\n {\n printf(\"->%c\", G->Vertex[j]);\n G->isTrav[j] = 1;\n QueueIn(&Q, j);\n }\n }\n }\n }\n \n int main()\n {\n MatrixGraph G;\n int i, j;\n char select;\n do\n {\n printf(\"输入生成图的类型: (0:无向图, 1:有向图)\");\n fflush(stdin);\n scanf(\"%d\", &G.GraphType);\n printf(\"输入图的顶点数量和边数量\");\n fflush(stdin);\n scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum);\n for(i=0; i 个人博客 欢迎来访: http://zj2626.github.io\n","slug":"20170922_map","published":1,"date":"2020-01-15T05:50:39.638Z","updated":"2021-03-10T13:50:15.310Z","layout":"post","photos":[],"link":"","_id":"ckm3inve0007k24ujk3rf23h5","content":"
\n

##图: 图是由顶点集(VertexSet)和边集(EdgeSet)组成,针对图G,顶点集和边集分别记为V(G)和E(G)。依据图的边集是否为有向,可把图分为有向图和无向图,根据图是否有权重,可以分为有权图和无权图。

\n
\n

线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,而图结构中的元素则是“多对多”的关系

\n
顶点(Vertex)、弧(Arc)、弧头(初始点)、弧尾(终结点)、边(Edge)、有向图(Directed graph)、\n无向图(Undigraph)、完全图(Completed grapg)、有向完全图、稀疏图(Sparse graph)、\n稠密图(Dense graph)、权(weigh)、网(network)、无向网、有向网、子图(Subgraph)、\n邻接点(Adjacent)、度(Degree)、入度(Indegree)、出度(Outdegree)、路径(path)、\n回路(环)、简单路径、简单回路(简单环)、连通、连通图(Connected graph)、连通分量(Connected Component)、\n强连通图、强连通分量(有向图中的极大强连通子图)、生成树、极小连通子图、有向树。\n
\n
\n

##图的存储:

\n
\n
    \n
  1. 邻接矩阵:用一个二维数组表示图中顶点和顶点,边的关系;形成的矩阵中可以自定义边的权值表示, 例如:0表示没有边, 其他大于0的数n表示两个顶点有边且权值为n
  2. \n
\n

  \"图片说明\"

\n

  \"图片说明\"

\n
    \n
  1. 邻接表
  2. \n
\n
接矩阵与邻接表相比,它会造成空间的一定损失,它需要为每个顶点都分配n个边的空间,\n其实有很多边都是不存在边,但是邻接表的实现就不一样,它只关心存在的边,不关心不存在的边。\n

  \"图片说明\"

\n
\n

##图的遍历:

\n
\n

1.广度优先

\n
1.从isTrav数组中选择一个未被访问的邻接点,标记为已访问\n2.依次访问Vi的所有未被访问的邻接点,并标记为已被访问\n3.从邻接点出发进行广度优先遍历,直到图中所有和Vi有路径相通的顶点都被访问\n4.重复1-3的步骤直到所有的顶点都被访问\n

2.深度优先

\n
1.从isTrav数组中选择一个未被访问的邻接点Vi,标记为已访问\n2.从Vi邻接点出发进行深度优先遍历\n3.重复2步骤,直到所有的和Vi有路径相通的顶点都被访问过\n4.重复1-3操作,直到所有顶点都被访问过\n
\n

##代码演示

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

//邻接矩阵
#include <stdio.h>
#include <stdlib.h>

#define VERTEX_MAX 26
#define MAXVALUE 32767 //表示当权值为MAXVALUE时,两个顶点没有相连

typedef struct
{
char Vertex[VERTEX_MAX]; //保存顶点信息(序号或字母)
int Edges[VERTEX_MAX][VERTEX_MAX]; //保存边的权
int isTrav[VERTEX_MAX]; //遍历标志
int VertexNum; //顶点数量
int EdgeNum; //边数量
int GraphType; //图的类型(0无向图 1有向图)
} MatrixGraph; //定义邻接矩阵图结构

void CreateMatrixGraph(MatrixGraph *G); //创建邻接矩阵图
void OutMatrix(MatrixGraph *G); //输出邻接矩阵

void CreateMatrixGraph(MatrixGraph *G)
{
int i, j, k, weight;
char start, end; //边的起始顶点

printf(\"输入各顶点的信息:\\n\"); //输入顶点
for(i=0; i<G->VertexNum; i++)
{
fflush(stdin);
printf(\"第%d个顶点信息:\", i+1);
scanf(\"%c\", &(G->Vertex[i]));
}

printf(\"输入构成各边的两个顶点以及权值:\\n\");

for(k=0; k<G->EdgeNum; k++)
{
printf(\"第%d条边:\", k+1);
fflush(stdin);
scanf(\"%c %c %d\", &start, &end, &weight);
for(i=0; start!=G->Vertex[i]; i++); //查找已有的顶点中是否包含当前的\"起始顶点\"
for(j=0; end!=G->Vertex[j]; j++); //同上

// printf(\"%d %d\", i, j);
G->Edges[i][j] = weight; //对应位置保存权值,表示有一条边
if(G->GraphType == 0) //判断是不是无向图
{
G->Edges[j][i] = weight; //在对角位置保存权值
}
}
}

void OutMatrix(MatrixGraph *G)
{
int i, j;

printf(\" \");
for(j=0; j<G->VertexNum; j++)
printf(\"\\t%c\", G->Vertex[j]); //输出顶点信息
printf(\"\\n\");

for(i=0; i<G->VertexNum; i++)
{
printf(\"%c\", G->Vertex[i]);
for(j=0; j<G->VertexNum; j++)
{
if(G->Edges[i][j] >= MAXVALUE) //如果权值为最大值
printf(\"\\t #\"); //输出无穷大符号
else
printf(\"\\t%d\", G->Edges[i][j]); //输出边的权值
}

printf(\"\\n\");
}
}

int main()
{
MatrixGraph G;
int i, j;
printf(\"输入生成图的类型 (0无向图 1有向图)\");
scanf(\"%d\", &G.GraphType);
printf(\"输入图的顶点数量和边数量\");
scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum); //输入图顶点数和边数
for(i=0; i<G.VertexNum; i++)
{
for(j=0; j<G.VertexNum; j++)
{
G.Edges[i][j] = MAXVALUE; //初始化元素值为最大值
}
}
CreateMatrixGraph(&G);

OutMatrix(&G);

return 0;
}
\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

//邻接表
#include <stdio.h>
#include <stdlib.h>

#define VERTEX_MAX 26

typedef struct edgeNode
{
int Vertex; //顶点信息
int weight; //权值
struct edgeNode *next; //下一个顶点地址指针
}EdgeNode; //边的结构

typedef struct
{
EdgeNode* AdjList[VERTEX_MAX]; //顶点指针
int VextexNum, EdgeNum; //顶点数量,边数量
int GraphType; //图的类型(0无向图 1有向图)
}ListGraph; //图的结构

void CreateGraph(ListGraph *G);
void OutList(ListGraph *G);

void CreateGraph(ListGraph *G)
{
int i, weight;
int start, end;
EdgeNode *s;
for(i=0; i<=G->VextexNum; i++) //初始化
G->AdjList[i] = NULL;

printf(\"输入构成各边的两个顶点以及权值:\\n\");

for(i=0; i<G->EdgeNum; i++)
{
printf(\"第%d条边 开始顶点 结束顶点 权值\", i+1);
fflush(stdin);
scanf(\"%d %d %d\", &start, &end, &weight);
s = (EdgeNode *)malloc(sizeof(EdgeNode));
s->next = G->AdjList[start];
s->Vertex = end;
s->weight = weight;
G->AdjList[start] = s;//把生成的边信息赋值给图

if(G->GraphType == 0) //判断是不是有向图
{
s = (EdgeNode *)malloc(sizeof(EdgeNode));
s->next = G->AdjList[end];
s->Vertex = start;
s->weight = weight;
G->AdjList[end] = s;//把生成的边信息赋值给图
}
}
}

void OutList(ListGraph *G)
{
int i;
EdgeNode *s;
for(i=0; i<=G->VextexNum; i++)
{
printf(\"顶点%d\", i);
s = G->AdjList[i];
while(s)
{
printf(\"->%d(%d)\", s->Vertex, s->weight);
s = s->next;
}
printf(\"\\n\");
}
}

int main()
{
ListGraph G;
printf(\"输入生成图的类型 (0无向图 1有向图)\");
scanf(\"%d\", &G.GraphType);
printf(\"输入图的顶点数量和边数量\");
scanf(\"%d %d\", &G.VextexNum, &G.EdgeNum);

CreateGraph(&G);
OutList(&G);

return 0;
}
\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

//深度优先和广度优先
#include <stdio.h>
#include <stdlib.h>
#include \"AdjMatrixGraph.h\"

#define QUEUE_MAXSIZE 30 //队列的最大容量

typedef struct
{
int Data[QUEUE_MAXSIZE];//数据域
int head; //队头指针
int tail; //队尾指针
}SeqQueue; //队列结构

//队列操作函数
void QueueInit(SeqQueue *q); //初始化一个队列
int QueueIsEmpty(SeqQueue q); //判断队列是否为空
int QueueIn(SeqQueue *q, int n); //入队
int QueueOut(SeqQueue *q, int *ch); //出队

//图操作函数
void DFSTraverse(MatrixGraph *G); //深度优先遍历
void BFSTraverse(MatrixGraph *G); //广度优先遍历
void DFSM(MatrixGraph *G, int i);
void BFSM(MatrixGraph *G, int i);

void QueueInit(SeqQueue *q){
q->head = q->tail = 0;
}

int QueueIsEmpty(SeqQueue q)
{
return q.head = q.tail;
}

int QueueIn(SeqQueue *q, int n)
{
if((q->tail + 1) % q->head != 0)
{
q->Data[q->tail] = n;
q->tail = (q->tail + 1) % QUEUE_MAXSIZE;
return 1;
}
else
{
return 0;
}
}

int QueueOut(SeqQueue *q, int *ch)
{
if(q->head != q->tail)
{
*ch = q->Data[q->head];
q->head = (q->head + 1) % QUEUE_MAXSIZE;
return 1;
}
else
{
return 0;
}
}

void DFSTraverse(MatrixGraph *G)
{
int i;
for(i=0; i<G->VertexNum; i++)
{
G->isTrav[i] = 0;
}

printf(\"深度优先遍历\");
for(i=0; i<G->VertexNum; i++)
{
if(!G->isTrav[i])
{
DFSM(G, i);
}
}
}

void BFSTraverse(MatrixGraph *G)
{
int i;
for(i=0; i<G->VertexNum; i++)
{
G->isTrav[i] = 0;
}

printf(\"广度优先遍历\");
for(i=0; i<G->VertexNum; i++)
{
if(!G->isTrav[i])
{
BFSM(G, i);
}
}
}

void DFSM(MatrixGraph *G, int i)
{
int j;
G->isTrav[i] = i;
printf(\"->%c\", G->Vertex[i]);

for(j=0; j<G->VertexNum; j++)
{
if(G->Edges[i][j] != MAXVALUE && !G->isTrav[i])
{
DFSM(G, j);
}
}
}

void BFSM(MatrixGraph *G, int k)
{
int i, j;
SeqQueue Q;
QueueInit(&Q);

G->isTrav[k] = 1;
printf(\"->%c\", G->Vertex[k]);

QueueIn(&Q, k);
while(!QueueIsEmpty(Q))
{
QueueOut(&Q, &i);
for(j=0; j<G->VertexNum; j++)
{
if(G->Edges[i][j] != MAXVALUE && !G->isTrav[j])
{
printf(\"->%c\", G->Vertex[j]);
G->isTrav[j] = 1;
QueueIn(&Q, j);
}
}
}
}

int main()
{
MatrixGraph G;
int i, j;
char select;
do
{
printf(\"输入生成图的类型: (0:无向图, 1:有向图)\");
fflush(stdin);
scanf(\"%d\", &G.GraphType);
printf(\"输入图的顶点数量和边数量\");
fflush(stdin);
scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum);
for(i=0; i<G.VertexNum; i++)
{
for(j=0; j<G.VertexNum; j++)
{
G.Edges[i][j] = MAXVALUE;
}
}

CreateMatrixGraph(&G);

printf(\"邻接矩阵数据:\\n\");
OutMatrix(&G);
DFSTraverse(&G);
BFSTraverse(&G);

printf(\"两种遍历结束\");
fflush(stdin);
scanf(\"%c\", &select);
}while(select!= 'N' && select != '\\n');
return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

##图: 图是由顶点集(VertexSet)和边集(EdgeSet)组成,针对图G,顶点集和边集分别记为V(G)和E(G)。依据图的边集是否为有向,可把图分为有向图和无向图,根据图是否有权重,可以分为有权图和无权图。

\n
\n

线性表中的元素是“一对一”的关系,树中的元素是“一对多”的关系,而图结构中的元素则是“多对多”的关系

\n
顶点(Vertex)、弧(Arc)、弧头(初始点)、弧尾(终结点)、边(Edge)、有向图(Directed graph)、\n无向图(Undigraph)、完全图(Completed grapg)、有向完全图、稀疏图(Sparse graph)、\n稠密图(Dense graph)、权(weigh)、网(network)、无向网、有向网、子图(Subgraph)、\n邻接点(Adjacent)、度(Degree)、入度(Indegree)、出度(Outdegree)、路径(path)、\n回路(环)、简单路径、简单回路(简单环)、连通、连通图(Connected graph)、连通分量(Connected Component)、\n强连通图、强连通分量(有向图中的极大强连通子图)、生成树、极小连通子图、有向树。\n
","more":"
\n

##图的存储:

\n
\n
    \n
  1. 邻接矩阵:用一个二维数组表示图中顶点和顶点,边的关系;形成的矩阵中可以自定义边的权值表示, 例如:0表示没有边, 其他大于0的数n表示两个顶点有边且权值为n
  2. \n
\n

  \"图片说明\"

\n

  \"图片说明\"

\n
    \n
  1. 邻接表
  2. \n
\n
接矩阵与邻接表相比,它会造成空间的一定损失,它需要为每个顶点都分配n个边的空间,\n其实有很多边都是不存在边,但是邻接表的实现就不一样,它只关心存在的边,不关心不存在的边。\n

  \"图片说明\"

\n
\n

##图的遍历:

\n
\n

1.广度优先

\n
1.从isTrav数组中选择一个未被访问的邻接点,标记为已访问\n2.依次访问Vi的所有未被访问的邻接点,并标记为已被访问\n3.从邻接点出发进行广度优先遍历,直到图中所有和Vi有路径相通的顶点都被访问\n4.重复1-3的步骤直到所有的顶点都被访问\n

2.深度优先

\n
1.从isTrav数组中选择一个未被访问的邻接点Vi,标记为已访问\n2.从Vi邻接点出发进行深度优先遍历\n3.重复2步骤,直到所有的和Vi有路径相通的顶点都被访问过\n4.重复1-3操作,直到所有顶点都被访问过\n
\n

##代码演示

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

//邻接矩阵
#include <stdio.h>
#include <stdlib.h>

#define VERTEX_MAX 26
#define MAXVALUE 32767 //表示当权值为MAXVALUE时,两个顶点没有相连

typedef struct
{
char Vertex[VERTEX_MAX]; //保存顶点信息(序号或字母)
int Edges[VERTEX_MAX][VERTEX_MAX]; //保存边的权
int isTrav[VERTEX_MAX]; //遍历标志
int VertexNum; //顶点数量
int EdgeNum; //边数量
int GraphType; //图的类型(0无向图 1有向图)
} MatrixGraph; //定义邻接矩阵图结构

void CreateMatrixGraph(MatrixGraph *G); //创建邻接矩阵图
void OutMatrix(MatrixGraph *G); //输出邻接矩阵

void CreateMatrixGraph(MatrixGraph *G)
{
int i, j, k, weight;
char start, end; //边的起始顶点

printf(\"输入各顶点的信息:\\n\"); //输入顶点
for(i=0; i<G->VertexNum; i++)
{
fflush(stdin);
printf(\"第%d个顶点信息:\", i+1);
scanf(\"%c\", &(G->Vertex[i]));
}

printf(\"输入构成各边的两个顶点以及权值:\\n\");

for(k=0; k<G->EdgeNum; k++)
{
printf(\"第%d条边:\", k+1);
fflush(stdin);
scanf(\"%c %c %d\", &start, &end, &weight);
for(i=0; start!=G->Vertex[i]; i++); //查找已有的顶点中是否包含当前的\"起始顶点\"
for(j=0; end!=G->Vertex[j]; j++); //同上

// printf(\"%d %d\", i, j);
G->Edges[i][j] = weight; //对应位置保存权值,表示有一条边
if(G->GraphType == 0) //判断是不是无向图
{
G->Edges[j][i] = weight; //在对角位置保存权值
}
}
}

void OutMatrix(MatrixGraph *G)
{
int i, j;

printf(\" \");
for(j=0; j<G->VertexNum; j++)
printf(\"\\t%c\", G->Vertex[j]); //输出顶点信息
printf(\"\\n\");

for(i=0; i<G->VertexNum; i++)
{
printf(\"%c\", G->Vertex[i]);
for(j=0; j<G->VertexNum; j++)
{
if(G->Edges[i][j] >= MAXVALUE) //如果权值为最大值
printf(\"\\t #\"); //输出无穷大符号
else
printf(\"\\t%d\", G->Edges[i][j]); //输出边的权值
}

printf(\"\\n\");
}
}

int main()
{
MatrixGraph G;
int i, j;
printf(\"输入生成图的类型 (0无向图 1有向图)\");
scanf(\"%d\", &G.GraphType);
printf(\"输入图的顶点数量和边数量\");
scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum); //输入图顶点数和边数
for(i=0; i<G.VertexNum; i++)
{
for(j=0; j<G.VertexNum; j++)
{
G.Edges[i][j] = MAXVALUE; //初始化元素值为最大值
}
}
CreateMatrixGraph(&G);

OutMatrix(&G);

return 0;
}
\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

//邻接表
#include <stdio.h>
#include <stdlib.h>

#define VERTEX_MAX 26

typedef struct edgeNode
{
int Vertex; //顶点信息
int weight; //权值
struct edgeNode *next; //下一个顶点地址指针
}EdgeNode; //边的结构

typedef struct
{
EdgeNode* AdjList[VERTEX_MAX]; //顶点指针
int VextexNum, EdgeNum; //顶点数量,边数量
int GraphType; //图的类型(0无向图 1有向图)
}ListGraph; //图的结构

void CreateGraph(ListGraph *G);
void OutList(ListGraph *G);

void CreateGraph(ListGraph *G)
{
int i, weight;
int start, end;
EdgeNode *s;
for(i=0; i<=G->VextexNum; i++) //初始化
G->AdjList[i] = NULL;

printf(\"输入构成各边的两个顶点以及权值:\\n\");

for(i=0; i<G->EdgeNum; i++)
{
printf(\"第%d条边 开始顶点 结束顶点 权值\", i+1);
fflush(stdin);
scanf(\"%d %d %d\", &start, &end, &weight);
s = (EdgeNode *)malloc(sizeof(EdgeNode));
s->next = G->AdjList[start];
s->Vertex = end;
s->weight = weight;
G->AdjList[start] = s;//把生成的边信息赋值给图

if(G->GraphType == 0) //判断是不是有向图
{
s = (EdgeNode *)malloc(sizeof(EdgeNode));
s->next = G->AdjList[end];
s->Vertex = start;
s->weight = weight;
G->AdjList[end] = s;//把生成的边信息赋值给图
}
}
}

void OutList(ListGraph *G)
{
int i;
EdgeNode *s;
for(i=0; i<=G->VextexNum; i++)
{
printf(\"顶点%d\", i);
s = G->AdjList[i];
while(s)
{
printf(\"->%d(%d)\", s->Vertex, s->weight);
s = s->next;
}
printf(\"\\n\");
}
}

int main()
{
ListGraph G;
printf(\"输入生成图的类型 (0无向图 1有向图)\");
scanf(\"%d\", &G.GraphType);
printf(\"输入图的顶点数量和边数量\");
scanf(\"%d %d\", &G.VextexNum, &G.EdgeNum);

CreateGraph(&G);
OutList(&G);

return 0;
}
\n
\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

//深度优先和广度优先
#include <stdio.h>
#include <stdlib.h>
#include \"AdjMatrixGraph.h\"

#define QUEUE_MAXSIZE 30 //队列的最大容量

typedef struct
{
int Data[QUEUE_MAXSIZE];//数据域
int head; //队头指针
int tail; //队尾指针
}SeqQueue; //队列结构

//队列操作函数
void QueueInit(SeqQueue *q); //初始化一个队列
int QueueIsEmpty(SeqQueue q); //判断队列是否为空
int QueueIn(SeqQueue *q, int n); //入队
int QueueOut(SeqQueue *q, int *ch); //出队

//图操作函数
void DFSTraverse(MatrixGraph *G); //深度优先遍历
void BFSTraverse(MatrixGraph *G); //广度优先遍历
void DFSM(MatrixGraph *G, int i);
void BFSM(MatrixGraph *G, int i);

void QueueInit(SeqQueue *q){
q->head = q->tail = 0;
}

int QueueIsEmpty(SeqQueue q)
{
return q.head = q.tail;
}

int QueueIn(SeqQueue *q, int n)
{
if((q->tail + 1) % q->head != 0)
{
q->Data[q->tail] = n;
q->tail = (q->tail + 1) % QUEUE_MAXSIZE;
return 1;
}
else
{
return 0;
}
}

int QueueOut(SeqQueue *q, int *ch)
{
if(q->head != q->tail)
{
*ch = q->Data[q->head];
q->head = (q->head + 1) % QUEUE_MAXSIZE;
return 1;
}
else
{
return 0;
}
}

void DFSTraverse(MatrixGraph *G)
{
int i;
for(i=0; i<G->VertexNum; i++)
{
G->isTrav[i] = 0;
}

printf(\"深度优先遍历\");
for(i=0; i<G->VertexNum; i++)
{
if(!G->isTrav[i])
{
DFSM(G, i);
}
}
}

void BFSTraverse(MatrixGraph *G)
{
int i;
for(i=0; i<G->VertexNum; i++)
{
G->isTrav[i] = 0;
}

printf(\"广度优先遍历\");
for(i=0; i<G->VertexNum; i++)
{
if(!G->isTrav[i])
{
BFSM(G, i);
}
}
}

void DFSM(MatrixGraph *G, int i)
{
int j;
G->isTrav[i] = i;
printf(\"->%c\", G->Vertex[i]);

for(j=0; j<G->VertexNum; j++)
{
if(G->Edges[i][j] != MAXVALUE && !G->isTrav[i])
{
DFSM(G, j);
}
}
}

void BFSM(MatrixGraph *G, int k)
{
int i, j;
SeqQueue Q;
QueueInit(&Q);

G->isTrav[k] = 1;
printf(\"->%c\", G->Vertex[k]);

QueueIn(&Q, k);
while(!QueueIsEmpty(Q))
{
QueueOut(&Q, &i);
for(j=0; j<G->VertexNum; j++)
{
if(G->Edges[i][j] != MAXVALUE && !G->isTrav[j])
{
printf(\"->%c\", G->Vertex[j]);
G->isTrav[j] = 1;
QueueIn(&Q, j);
}
}
}
}

int main()
{
MatrixGraph G;
int i, j;
char select;
do
{
printf(\"输入生成图的类型: (0:无向图, 1:有向图)\");
fflush(stdin);
scanf(\"%d\", &G.GraphType);
printf(\"输入图的顶点数量和边数量\");
fflush(stdin);
scanf(\"%d %d\", &G.VertexNum, &G.EdgeNum);
for(i=0; i<G.VertexNum; i++)
{
for(j=0; j<G.VertexNum; j++)
{
G.Edges[i][j] = MAXVALUE;
}
}

CreateMatrixGraph(&G);

printf(\"邻接矩阵数据:\\n\");
OutMatrix(&G);
DFSTraverse(&G);
BFSTraverse(&G);

printf(\"两种遍历结束\");
fflush(stdin);
scanf(\"%c\", &select);
}while(select!= 'N' && select != '\\n');
return 0;
}
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"Minor GC、Major GC和Full GC之间的区别","comments":1,"description":null,"_content":"\n1、动态编译(dynamic compilation)指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)。\n\n2、JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别。\n\n3、自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化。\n\n\n\n## 概述\n### JVM运行原理\n\n{% qnimg 20160812104144969.jpg title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)。\n即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。\n\n由于Java虚拟机规范并没有具体的约束规则去限制即使编译器应该如何实现,所以这部分功能完全是与虚拟机具体实现相关的内容,如无特殊说明,我们提到的编译器、即时编译器都是指Hotspot虚拟机内的即时编译器,虚拟机也是特指HotSpot虚拟机。\n\n### 为什么HotSpot虚拟机要使用解释器与编译器并存的架构?\n\n尽管并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机(如HotSpot),都同时包含解释器和编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。\n\n{% qnimg 20160812102841736.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n### 编译的时间开销\n\n解释器的执行,抽象的看是这样的:\n输入的代码 -> [ 解释器 解释执行 ] -> 执行结果\n而要JIT编译然后再执行的话,抽象的看则是:\n输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果\n说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。\nJIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。\n所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。\n\n怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”\n1、只被调用一次,例如类的构造器(class initializer,())\n2、没有循环\n\n**对只执行一次的代码做JIT编译再执行,可以说是得不偿失。**\n**对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。**\n**只有对频繁执行的代码,JIT编译才能保证有正面的收益。**\n\n### 编译的空间开销\n\n对一般的Java方法而言,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的。同上面说的时间开销一样,这里的空间开销也是,只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”。\n这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。\n\n### 为何HotSpot虚拟机要实现两个不同的即时编译器?\n\nHotSpot虚拟机中内置了两个即时编译器:Client Complier和Server Complier,简称为C1、C2编译器,分别用在客户端和服务端。目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。程序使用哪个编译器,取决于虚拟机运行的模式。HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。\n**用Client Complier获取更高的编译速度,用Server Complier 来获取更好的编译质量**;为什么提供多个即时编译器与为什么提供多个垃圾收集器类似,都是为了适应不同的应用场景。\n\n### 哪些程序代码会被编译为本地代码?如何编译为本地代码?\n\n程序中的代码只有是热点代码时,才会编译为本地代码,那么什么是热点代码呢?\n运行过程中会被即时编译器编译的“热点代码”有两类:\n1、被多次调用的方法。\n2、被多次执行的循环体。\n两种情况,编译器都是以整个方法作为编译对象。 这种编译方法因为编译发生在方法执行过程之中,因此形象的称之为栈上替换(On Stack Replacement,OSR),即方法栈帧还在栈上,方法就被替换了\n\n### 如何判断方法或一段代码或是不是热点代码呢?\n\n要知道方法或一段代码是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)。\n目前主要的热点探测方式有以下两种:\n\n* 基于采样的热点探测\n\n采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。\n\n* 基于计数器的热点探测\n\n采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。\n\n### HotSpot虚拟机中使用的是哪钟热点检测方式呢?\n\n在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。\n> 方法调用计数器\n\n{% qnimg 20160812101630575.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n \n 顾名思义,这个计数器用于统计方法被调用的次数。\n 当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。\n 如果不做任何设置,执行引擎并不会同步等待编译请求完成,而是继续进行解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。\n\n> 回边计数器\n\n{% qnimg 20160812102239062.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 它的作用就是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”。\n \n \n### 如何编译为本地代码?\n\nServer Compiler和Client Compiler两个编译器的编译过程是不一样的。\n对Client Compiler来说,它是一个简单快速的编译器,主要关注点在于局部优化,而放弃许多耗时较长的全局优化手段。\n而Server Compiler则是专门面向服务器端的,并为服务端的性能配置特别调整过的编译器,是一个充分优化过的高级编译器。\n\n参考:\n《深入理解Java虚拟机》\nhttp://blog.csdn.net/u010412719/article/details/47008717\nhttps://zhuanlan.zhihu.com/p/19977592\nhttp://www.zhihu.com/question/37389356/answer/73820511\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170924_JIT.md","raw":"---\ntitle: Minor GC、Major GC和Full GC之间的区别\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n \n---\n\n1、动态编译(dynamic compilation)指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)。\n\n2、JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别。\n\n3、自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化。\n\n\n\n## 概述\n### JVM运行原理\n\n{% qnimg 20160812104144969.jpg title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)。\n即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。\n\n由于Java虚拟机规范并没有具体的约束规则去限制即使编译器应该如何实现,所以这部分功能完全是与虚拟机具体实现相关的内容,如无特殊说明,我们提到的编译器、即时编译器都是指Hotspot虚拟机内的即时编译器,虚拟机也是特指HotSpot虚拟机。\n\n### 为什么HotSpot虚拟机要使用解释器与编译器并存的架构?\n\n尽管并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机(如HotSpot),都同时包含解释器和编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。\n\n{% qnimg 20160812102841736.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n### 编译的时间开销\n\n解释器的执行,抽象的看是这样的:\n输入的代码 -> [ 解释器 解释执行 ] -> 执行结果\n而要JIT编译然后再执行的话,抽象的看则是:\n输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果\n说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。\nJIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。\n所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。\n\n怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”\n1、只被调用一次,例如类的构造器(class initializer,())\n2、没有循环\n\n**对只执行一次的代码做JIT编译再执行,可以说是得不偿失。**\n**对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。**\n**只有对频繁执行的代码,JIT编译才能保证有正面的收益。**\n\n### 编译的空间开销\n\n对一般的Java方法而言,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的。同上面说的时间开销一样,这里的空间开销也是,只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”。\n这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。\n\n### 为何HotSpot虚拟机要实现两个不同的即时编译器?\n\nHotSpot虚拟机中内置了两个即时编译器:Client Complier和Server Complier,简称为C1、C2编译器,分别用在客户端和服务端。目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。程序使用哪个编译器,取决于虚拟机运行的模式。HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。\n**用Client Complier获取更高的编译速度,用Server Complier 来获取更好的编译质量**;为什么提供多个即时编译器与为什么提供多个垃圾收集器类似,都是为了适应不同的应用场景。\n\n### 哪些程序代码会被编译为本地代码?如何编译为本地代码?\n\n程序中的代码只有是热点代码时,才会编译为本地代码,那么什么是热点代码呢?\n运行过程中会被即时编译器编译的“热点代码”有两类:\n1、被多次调用的方法。\n2、被多次执行的循环体。\n两种情况,编译器都是以整个方法作为编译对象。 这种编译方法因为编译发生在方法执行过程之中,因此形象的称之为栈上替换(On Stack Replacement,OSR),即方法栈帧还在栈上,方法就被替换了\n\n### 如何判断方法或一段代码或是不是热点代码呢?\n\n要知道方法或一段代码是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)。\n目前主要的热点探测方式有以下两种:\n\n* 基于采样的热点探测\n\n采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。\n\n* 基于计数器的热点探测\n\n采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。\n\n### HotSpot虚拟机中使用的是哪钟热点检测方式呢?\n\n在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。\n> 方法调用计数器\n\n{% qnimg 20160812101630575.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n \n 顾名思义,这个计数器用于统计方法被调用的次数。\n 当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。\n 如果不做任何设置,执行引擎并不会同步等待编译请求完成,而是继续进行解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。\n\n> 回边计数器\n\n{% qnimg 20160812102239062.png title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n 它的作用就是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”。\n \n \n### 如何编译为本地代码?\n\nServer Compiler和Client Compiler两个编译器的编译过程是不一样的。\n对Client Compiler来说,它是一个简单快速的编译器,主要关注点在于局部优化,而放弃许多耗时较长的全局优化手段。\n而Server Compiler则是专门面向服务器端的,并为服务端的性能配置特别调整过的编译器,是一个充分优化过的高级编译器。\n\n参考:\n《深入理解Java虚拟机》\nhttp://blog.csdn.net/u010412719/article/details/47008717\nhttps://zhuanlan.zhihu.com/p/19977592\nhttp://www.zhihu.com/question/37389356/answer/73820511\n\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170924_JIT","published":1,"date":"2020-01-15T05:50:39.639Z","updated":"2021-03-10T13:50:15.367Z","layout":"post","photos":[],"link":"","_id":"ckm3inve2007n24ujvds7ganm","content":"

1、动态编译(dynamic compilation)指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)。

\n

2、JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别。

\n

3、自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化。

\n\n

概述

JVM运行原理

\"图片说明\"\n

在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)。
即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。

\n

由于Java虚拟机规范并没有具体的约束规则去限制即使编译器应该如何实现,所以这部分功能完全是与虚拟机具体实现相关的内容,如无特殊说明,我们提到的编译器、即时编译器都是指Hotspot虚拟机内的即时编译器,虚拟机也是特指HotSpot虚拟机。

\n

为什么HotSpot虚拟机要使用解释器与编译器并存的架构?

尽管并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机(如HotSpot),都同时包含解释器和编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。

\n\"图片说明\"\n

编译的时间开销

解释器的执行,抽象的看是这样的:
输入的代码 -> [ 解释器 解释执行 ] -> 执行结果
而要JIT编译然后再执行的话,抽象的看则是:
输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果
说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。
JIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。
所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。

\n

怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”
1、只被调用一次,例如类的构造器(class initializer,())
2、没有循环

\n

对只执行一次的代码做JIT编译再执行,可以说是得不偿失。
对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。
只有对频繁执行的代码,JIT编译才能保证有正面的收益。

\n

编译的空间开销

对一般的Java方法而言,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的。同上面说的时间开销一样,这里的空间开销也是,只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”。
这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。

\n

为何HotSpot虚拟机要实现两个不同的即时编译器?

HotSpot虚拟机中内置了两个即时编译器:Client Complier和Server Complier,简称为C1、C2编译器,分别用在客户端和服务端。目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。程序使用哪个编译器,取决于虚拟机运行的模式。HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。
用Client Complier获取更高的编译速度,用Server Complier 来获取更好的编译质量;为什么提供多个即时编译器与为什么提供多个垃圾收集器类似,都是为了适应不同的应用场景。

\n

哪些程序代码会被编译为本地代码?如何编译为本地代码?

程序中的代码只有是热点代码时,才会编译为本地代码,那么什么是热点代码呢?
运行过程中会被即时编译器编译的“热点代码”有两类:
1、被多次调用的方法。
2、被多次执行的循环体。
两种情况,编译器都是以整个方法作为编译对象。 这种编译方法因为编译发生在方法执行过程之中,因此形象的称之为栈上替换(On Stack Replacement,OSR),即方法栈帧还在栈上,方法就被替换了

\n

如何判断方法或一段代码或是不是热点代码呢?

要知道方法或一段代码是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)。
目前主要的热点探测方式有以下两种:

\n
    \n
  • 基于采样的热点探测
  • \n
\n

采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。

\n
    \n
  • 基于计数器的热点探测
  • \n
\n

采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。

\n

HotSpot虚拟机中使用的是哪钟热点检测方式呢?

在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。

\n
\n

方法调用计数器

\n
\n\"图片说明\"\n
顾名思义,这个计数器用于统计方法被调用的次数。\n当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。\n如果不做任何设置,执行引擎并不会同步等待编译请求完成,而是继续进行解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。\n
\n

回边计数器

\n
\n\"图片说明\"\n
它的作用就是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”。\n

如何编译为本地代码?

Server Compiler和Client Compiler两个编译器的编译过程是不一样的。
对Client Compiler来说,它是一个简单快速的编译器,主要关注点在于局部优化,而放弃许多耗时较长的全局优化手段。
而Server Compiler则是专门面向服务器端的,并为服务端的性能配置特别调整过的编译器,是一个充分优化过的高级编译器。

\n

参考:
《深入理解Java虚拟机》
http://blog.csdn.net/u010412719/article/details/47008717
https://zhuanlan.zhihu.com/p/19977592
http://www.zhihu.com/question/37389356/answer/73820511

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"

1、动态编译(dynamic compilation)指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)。

\n

2、JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别。

\n

3、自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化。

","more":"

概述

JVM运行原理

\"图片说明\"\n

在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)。
即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。

\n

由于Java虚拟机规范并没有具体的约束规则去限制即使编译器应该如何实现,所以这部分功能完全是与虚拟机具体实现相关的内容,如无特殊说明,我们提到的编译器、即时编译器都是指Hotspot虚拟机内的即时编译器,虚拟机也是特指HotSpot虚拟机。

\n

为什么HotSpot虚拟机要使用解释器与编译器并存的架构?

尽管并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机(如HotSpot),都同时包含解释器和编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。

\n\"图片说明\"\n

编译的时间开销

解释器的执行,抽象的看是这样的:
输入的代码 -> [ 解释器 解释执行 ] -> 执行结果
而要JIT编译然后再执行的话,抽象的看则是:
输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果
说JIT比解释快,其实说的是“执行编译后的代码”比“解释器解释执行”要快,并不是说“编译”这个动作比“解释”这个动作快。
JIT编译再怎么快,至少也比解释执行一次略慢一些,而要得到最后的执行结果还得再经过一个“执行编译后的代码”的过程。
所以,对“只执行一次”的代码而言,解释执行其实总是比JIT编译执行要快。

\n

怎么算是“只执行一次的代码”呢?粗略说,下面两个条件同时满足时就是严格的“只执行一次”
1、只被调用一次,例如类的构造器(class initializer,())
2、没有循环

\n

对只执行一次的代码做JIT编译再执行,可以说是得不偿失。
对只执行少量次数的代码,JIT编译带来的执行速度的提升也未必能抵消掉最初编译带来的开销。
只有对频繁执行的代码,JIT编译才能保证有正面的收益。

\n

编译的空间开销

对一般的Java方法而言,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的。同上面说的时间开销一样,这里的空间开销也是,只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”。
这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。

\n

为何HotSpot虚拟机要实现两个不同的即时编译器?

HotSpot虚拟机中内置了两个即时编译器:Client Complier和Server Complier,简称为C1、C2编译器,分别用在客户端和服务端。目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。程序使用哪个编译器,取决于虚拟机运行的模式。HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。
用Client Complier获取更高的编译速度,用Server Complier 来获取更好的编译质量;为什么提供多个即时编译器与为什么提供多个垃圾收集器类似,都是为了适应不同的应用场景。

\n

哪些程序代码会被编译为本地代码?如何编译为本地代码?

程序中的代码只有是热点代码时,才会编译为本地代码,那么什么是热点代码呢?
运行过程中会被即时编译器编译的“热点代码”有两类:
1、被多次调用的方法。
2、被多次执行的循环体。
两种情况,编译器都是以整个方法作为编译对象。 这种编译方法因为编译发生在方法执行过程之中,因此形象的称之为栈上替换(On Stack Replacement,OSR),即方法栈帧还在栈上,方法就被替换了

\n

如何判断方法或一段代码或是不是热点代码呢?

要知道方法或一段代码是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)。
目前主要的热点探测方式有以下两种:

\n
    \n
  • 基于采样的热点探测
  • \n
\n

采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。

\n
    \n
  • 基于计数器的热点探测
  • \n
\n

采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。

\n

HotSpot虚拟机中使用的是哪钟热点检测方式呢?

在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。

\n
\n

方法调用计数器

\n
\n\"图片说明\"\n
顾名思义,这个计数器用于统计方法被调用的次数。\n当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。\n如果不做任何设置,执行引擎并不会同步等待编译请求完成,而是继续进行解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。\n
\n

回边计数器

\n
\n\"图片说明\"\n
它的作用就是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”。\n

如何编译为本地代码?

Server Compiler和Client Compiler两个编译器的编译过程是不一样的。
对Client Compiler来说,它是一个简单快速的编译器,主要关注点在于局部优化,而放弃许多耗时较长的全局优化手段。
而Server Compiler则是专门面向服务器端的,并为服务端的性能配置特别调整过的编译器,是一个充分优化过的高级编译器。

\n

参考:
《深入理解Java虚拟机》
http://blog.csdn.net/u010412719/article/details/47008717
https://zhuanlan.zhihu.com/p/19977592
http://www.zhihu.com/question/37389356/answer/73820511

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"Minor GC、Major GC和Full GC之间的区别","comments":1,"description":null,"_content":"\n> 转载链接地址: http://www.importnew.com/15820.html\n\n\n\n\n在 Plumbr 从事 GC 暂停检测相关功能的工作时,我被迫用自己的方式,通过大量文章、书籍和演讲来介绍我所做的工作。在整个过程中,经常对 Minor、Major、和 Full GC 事件的使用感到困惑。这也是我写这篇博客的原因,我希望能清楚地解释这其中的一些疑惑。\n\n文章要求读者熟悉 JVM 内置的通用垃圾回收原则。堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,代假设和其他不同的 GC 算法超出了本文讨论的范围。\n\n{% qnimg fd0c0db33776f042f62e5386131e487c.jpg title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n## Minor GC\n\n从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:\n\n当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。\n内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。\n执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。\n质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。\n所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。\n\n## Major GC vs Full GC\n\n大家应该注意到,目前,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义。但是我们一看就知道这些在我们已经知道的基础之上做出的定义是正确的,Minor GC 清理年轻带内存应该被设计得简单:\n\nMajor GC 是清理老年代。\nFull GC 是清理整个堆空间—包括年轻代和老年代。\n很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。\n\n这使得我们不用去关心到底是叫 Major GC 还是 Full GC,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。\n\n这种混乱甚至内置到 JVM 标准工具。下面一个例子很好的解释了我的意思。让我们比较两个不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中运行时输出的跟踪记录。\n\n----\n> 第一次尝试通过 jstat 输出:\n\nmy-precious: me$ jstat -gc -t 4235 1s\n\n\nTime S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT \n 5.7 34048.0 34048.0 0.0 34048.0 272640.0 194699.7 1756416.0 181419.9 18304.0 17865.1 2688.0 2497.6 3 0.275 0 0.000 0.275\n 6.7 34048.0 34048.0 34048.0 0.0 272640.0 247555.4 1756416.0 263447.9 18816.0 18123.3 2688.0 2523.1 4 0.359 0 0.000 0.359\n 7.7 34048.0 34048.0 0.0 34048.0 272640.0 257729.3 1756416.0 345109.8 19072.0 18396.6 2688.0 2550.3 5 0.451 0 0.000 0.451\n 8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0 444982.5 19456.0 18681.3 2816.0 2575.8 7 0.550 0 0.000 0.550\n 9.7 34048.0 34048.0 34046.7 0.0 272640.0 16777.0 1756416.0 587906.3 20096.0 19235.1 2944.0 2631.8 8 0.720 0 0.000 0.720\n10.7 34048.0 34048.0 0.0 34046.2 272640.0 80171.6 1756416.0 664913.4 20352.0 19495.9 2944.0 2657.4 9 0.810 0 0.000 0.810\n11.7 34048.0 34048.0 34048.0 0.0 272640.0 129480.8 1756416.0 745100.2 20608.0 19704.5 2944.0 2678.4 10 0.896 0 0.000 0.896\n12.7 34048.0 34048.0 0.0 34046.6 272640.0 164070.7 1756416.0 822073.7 20992.0 19937.1 3072.0 2702.8 11 0.978 0 0.000 0.978\n13.7 34048.0 34048.0 34048.0 0.0 272640.0 211949.9 1756416.0 897364.4 21248.0 20179.6 3072.0 2728.1 12 1.087 1 0.004 1.091\n14.7 34048.0 34048.0 0.0 34047.1 272640.0 245801.5 1756416.0 597362.6 21504.0 20390.6 3072.0 2750.3 13 1.183 2 0.050 1.233\n15.7 34048.0 34048.0 0.0 34048.0 272640.0 21474.1 1756416.0 757347.0 22012.0 20792.0 3200.0 2791.0 15 1.336 2 0.050 1.386\n16.7 34048.0 34048.0 34047.0 0.0 272640.0 48378.0 1756416.0 838594.4 22268.0 21003.5 3200.0 2813.2 16 1.433 2 0.050 1.484\n\n> 这个片段是 JVM 启动后第17秒提取的。基于该信息,我们可以得出这样的结果,运行了12次 Minor GC、2次 Full GC,时间总跨度为50毫秒。通过 jconsole 或者 jvisualvm 这样的基于GUI的工具你能得到同样的结果。\n\njava -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer\n\n3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]\n4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs]\n... cut for brevity ...\n11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs]\n12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs]\n12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs]\n13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]\n13.102: [CMS-concurrent-mark-start]\n13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs]\n13.341: [CMS-concurrent-preclean-start]\n13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]\n13.350: [CMS-concurrent-abortable-preclean-start]\n13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs]\n14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs]\n14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]\n14.412: [CMS-concurrent-sweep-start]\n14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs]\n14.633: [CMS-concurrent-reset-start]\n14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00\n\n----\n\n\n> 在点头同意这个结论之前,让我们看看来自同一个 JVM 启动收集的垃圾收集日志的输出。显然- XX : + PrintGCDetails 告诉我们一个不同且更详细的故事:\n\n基于这些信息,我们可以看到12次 Minor GC 后开始有些和上面不一样了。没有运行两次 Full GC,这不同的地方在于单个 GC 在永久代中不同阶段运行了两次:\n\n* 最初的标记阶段,用了0.0041705秒也就是4ms左右。这个阶段会暂停“全世界( stop-the-world)”的事件,停止所有应用程序的线程,然后开始标记。\n* 并行执行标记和清洗阶段。这些都是和应用程序线程并行的。\n* 最后 Remark 阶段,花费了0.0462010秒约46ms。这个阶段会再次暂停所有的事件。\n* 并行执行清理操作。正如其名,此阶段也是并行的,不会停止其他线程。\n\n所以,正如我们从垃圾回收日志中所看到的那样,实际上只是执行了 Major GC 去清理老年代空间而已,而不是执行了两次 Full GC。\n\n如果你是后期做决 定的话,那么由 jstat 提供的数据会引导你做出正确的决策。它正确列出的两个暂停所有事件的情况,导致所有线程停止了共计50ms。但是如果你试图优化吞吐量,你会被误导的。清 单只列出了回收初始标记和最终 Remark 阶段,jstat的输出看不到那些并发完成的工作。\n\n## 结论\n\n考虑到这种情况,最好避免以 Minor、Major、Full GC 这种方式来思考问题。而应该监控应用延迟或者吞吐量,然后将 GC 事件和结果联系起来。\n\n随着这些 GC 事件的发生,你需要额外的关注某些信息,GC 事件是强制所有应用程序线程停止了还是并行的处理了部分事件。\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170924_JVM2.md","raw":"---\ntitle: Minor GC、Major GC和Full GC之间的区别\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n \n---\n\n> 转载链接地址: http://www.importnew.com/15820.html\n\n\n\n\n在 Plumbr 从事 GC 暂停检测相关功能的工作时,我被迫用自己的方式,通过大量文章、书籍和演讲来介绍我所做的工作。在整个过程中,经常对 Minor、Major、和 Full GC 事件的使用感到困惑。这也是我写这篇博客的原因,我希望能清楚地解释这其中的一些疑惑。\n\n文章要求读者熟悉 JVM 内置的通用垃圾回收原则。堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,代假设和其他不同的 GC 算法超出了本文讨论的范围。\n\n{% qnimg fd0c0db33776f042f62e5386131e487c.jpg title:图 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n## Minor GC\n\n从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:\n\n当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。\n内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。\n执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。\n质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。\n所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。\n\n## Major GC vs Full GC\n\n大家应该注意到,目前,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义。但是我们一看就知道这些在我们已经知道的基础之上做出的定义是正确的,Minor GC 清理年轻带内存应该被设计得简单:\n\nMajor GC 是清理老年代。\nFull GC 是清理整个堆空间—包括年轻代和老年代。\n很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。\n\n这使得我们不用去关心到底是叫 Major GC 还是 Full GC,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。\n\n这种混乱甚至内置到 JVM 标准工具。下面一个例子很好的解释了我的意思。让我们比较两个不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中运行时输出的跟踪记录。\n\n----\n> 第一次尝试通过 jstat 输出:\n\nmy-precious: me$ jstat -gc -t 4235 1s\n\n\nTime S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT \n 5.7 34048.0 34048.0 0.0 34048.0 272640.0 194699.7 1756416.0 181419.9 18304.0 17865.1 2688.0 2497.6 3 0.275 0 0.000 0.275\n 6.7 34048.0 34048.0 34048.0 0.0 272640.0 247555.4 1756416.0 263447.9 18816.0 18123.3 2688.0 2523.1 4 0.359 0 0.000 0.359\n 7.7 34048.0 34048.0 0.0 34048.0 272640.0 257729.3 1756416.0 345109.8 19072.0 18396.6 2688.0 2550.3 5 0.451 0 0.000 0.451\n 8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0 444982.5 19456.0 18681.3 2816.0 2575.8 7 0.550 0 0.000 0.550\n 9.7 34048.0 34048.0 34046.7 0.0 272640.0 16777.0 1756416.0 587906.3 20096.0 19235.1 2944.0 2631.8 8 0.720 0 0.000 0.720\n10.7 34048.0 34048.0 0.0 34046.2 272640.0 80171.6 1756416.0 664913.4 20352.0 19495.9 2944.0 2657.4 9 0.810 0 0.000 0.810\n11.7 34048.0 34048.0 34048.0 0.0 272640.0 129480.8 1756416.0 745100.2 20608.0 19704.5 2944.0 2678.4 10 0.896 0 0.000 0.896\n12.7 34048.0 34048.0 0.0 34046.6 272640.0 164070.7 1756416.0 822073.7 20992.0 19937.1 3072.0 2702.8 11 0.978 0 0.000 0.978\n13.7 34048.0 34048.0 34048.0 0.0 272640.0 211949.9 1756416.0 897364.4 21248.0 20179.6 3072.0 2728.1 12 1.087 1 0.004 1.091\n14.7 34048.0 34048.0 0.0 34047.1 272640.0 245801.5 1756416.0 597362.6 21504.0 20390.6 3072.0 2750.3 13 1.183 2 0.050 1.233\n15.7 34048.0 34048.0 0.0 34048.0 272640.0 21474.1 1756416.0 757347.0 22012.0 20792.0 3200.0 2791.0 15 1.336 2 0.050 1.386\n16.7 34048.0 34048.0 34047.0 0.0 272640.0 48378.0 1756416.0 838594.4 22268.0 21003.5 3200.0 2813.2 16 1.433 2 0.050 1.484\n\n> 这个片段是 JVM 启动后第17秒提取的。基于该信息,我们可以得出这样的结果,运行了12次 Minor GC、2次 Full GC,时间总跨度为50毫秒。通过 jconsole 或者 jvisualvm 这样的基于GUI的工具你能得到同样的结果。\n\njava -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer\n\n3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]\n4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs]\n... cut for brevity ...\n11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs]\n12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs]\n12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs]\n13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]\n13.102: [CMS-concurrent-mark-start]\n13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs]\n13.341: [CMS-concurrent-preclean-start]\n13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]\n13.350: [CMS-concurrent-abortable-preclean-start]\n13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs]\n14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs]\n14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]\n14.412: [CMS-concurrent-sweep-start]\n14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs]\n14.633: [CMS-concurrent-reset-start]\n14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00\n\n----\n\n\n> 在点头同意这个结论之前,让我们看看来自同一个 JVM 启动收集的垃圾收集日志的输出。显然- XX : + PrintGCDetails 告诉我们一个不同且更详细的故事:\n\n基于这些信息,我们可以看到12次 Minor GC 后开始有些和上面不一样了。没有运行两次 Full GC,这不同的地方在于单个 GC 在永久代中不同阶段运行了两次:\n\n* 最初的标记阶段,用了0.0041705秒也就是4ms左右。这个阶段会暂停“全世界( stop-the-world)”的事件,停止所有应用程序的线程,然后开始标记。\n* 并行执行标记和清洗阶段。这些都是和应用程序线程并行的。\n* 最后 Remark 阶段,花费了0.0462010秒约46ms。这个阶段会再次暂停所有的事件。\n* 并行执行清理操作。正如其名,此阶段也是并行的,不会停止其他线程。\n\n所以,正如我们从垃圾回收日志中所看到的那样,实际上只是执行了 Major GC 去清理老年代空间而已,而不是执行了两次 Full GC。\n\n如果你是后期做决 定的话,那么由 jstat 提供的数据会引导你做出正确的决策。它正确列出的两个暂停所有事件的情况,导致所有线程停止了共计50ms。但是如果你试图优化吞吐量,你会被误导的。清 单只列出了回收初始标记和最终 Remark 阶段,jstat的输出看不到那些并发完成的工作。\n\n## 结论\n\n考虑到这种情况,最好避免以 Minor、Major、Full GC 这种方式来思考问题。而应该监控应用延迟或者吞吐量,然后将 GC 事件和结果联系起来。\n\n随着这些 GC 事件的发生,你需要额外的关注某些信息,GC 事件是强制所有应用程序线程停止了还是并行的处理了部分事件。\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170924_JVM2","published":1,"date":"2020-01-15T05:50:39.642Z","updated":"2021-03-10T13:50:15.377Z","layout":"post","photos":[],"link":"","_id":"ckm3inve4007q24ujp1mjbmgx","content":"
\n

转载链接地址: http://www.importnew.com/15820.html

\n
\n\n

在 Plumbr 从事 GC 暂停检测相关功能的工作时,我被迫用自己的方式,通过大量文章、书籍和演讲来介绍我所做的工作。在整个过程中,经常对 Minor、Major、和 Full GC 事件的使用感到困惑。这也是我写这篇博客的原因,我希望能清楚地解释这其中的一些疑惑。

\n

文章要求读者熟悉 JVM 内置的通用垃圾回收原则。堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,代假设和其他不同的 GC 算法超出了本文讨论的范围。

\n\"图片说明\"\n

Minor GC

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:

\n

当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。

\n

Major GC vs Full GC

大家应该注意到,目前,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义。但是我们一看就知道这些在我们已经知道的基础之上做出的定义是正确的,Minor GC 清理年轻带内存应该被设计得简单:

\n

Major GC 是清理老年代。
Full GC 是清理整个堆空间—包括年轻代和老年代。
很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

\n

这使得我们不用去关心到底是叫 Major GC 还是 Full GC,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。

\n

这种混乱甚至内置到 JVM 标准工具。下面一个例子很好的解释了我的意思。让我们比较两个不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中运行时输出的跟踪记录。

\n
\n
\n

第一次尝试通过 jstat 输出:

\n
\n

my-precious: me$ jstat -gc -t 4235 1s

\n

Time S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5.7 34048.0 34048.0 0.0 34048.0 272640.0 194699.7 1756416.0 181419.9 18304.0 17865.1 2688.0 2497.6 3 0.275 0 0.000 0.275
6.7 34048.0 34048.0 34048.0 0.0 272640.0 247555.4 1756416.0 263447.9 18816.0 18123.3 2688.0 2523.1 4 0.359 0 0.000 0.359
7.7 34048.0 34048.0 0.0 34048.0 272640.0 257729.3 1756416.0 345109.8 19072.0 18396.6 2688.0 2550.3 5 0.451 0 0.000 0.451
8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0 444982.5 19456.0 18681.3 2816.0 2575.8 7 0.550 0 0.000 0.550
9.7 34048.0 34048.0 34046.7 0.0 272640.0 16777.0 1756416.0 587906.3 20096.0 19235.1 2944.0 2631.8 8 0.720 0 0.000 0.720
10.7 34048.0 34048.0 0.0 34046.2 272640.0 80171.6 1756416.0 664913.4 20352.0 19495.9 2944.0 2657.4 9 0.810 0 0.000 0.810
11.7 34048.0 34048.0 34048.0 0.0 272640.0 129480.8 1756416.0 745100.2 20608.0 19704.5 2944.0 2678.4 10 0.896 0 0.000 0.896
12.7 34048.0 34048.0 0.0 34046.6 272640.0 164070.7 1756416.0 822073.7 20992.0 19937.1 3072.0 2702.8 11 0.978 0 0.000 0.978
13.7 34048.0 34048.0 34048.0 0.0 272640.0 211949.9 1756416.0 897364.4 21248.0 20179.6 3072.0 2728.1 12 1.087 1 0.004 1.091
14.7 34048.0 34048.0 0.0 34047.1 272640.0 245801.5 1756416.0 597362.6 21504.0 20390.6 3072.0 2750.3 13 1.183 2 0.050 1.233
15.7 34048.0 34048.0 0.0 34048.0 272640.0 21474.1 1756416.0 757347.0 22012.0 20792.0 3200.0 2791.0 15 1.336 2 0.050 1.386
16.7 34048.0 34048.0 34047.0 0.0 272640.0 48378.0 1756416.0 838594.4 22268.0 21003.5 3200.0 2813.2 16 1.433 2 0.050 1.484

\n
\n

这个片段是 JVM 启动后第17秒提取的。基于该信息,我们可以得出这样的结果,运行了12次 Minor GC、2次 Full GC,时间总跨度为50毫秒。通过 jconsole 或者 jvisualvm 这样的基于GUI的工具你能得到同样的结果。

\n
\n

java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer

\n

3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs]
… cut for brevity …
11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs]
12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs]
12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs]
13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
13.102: [CMS-concurrent-mark-start]
13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs]
13.341: [CMS-concurrent-preclean-start]
13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
13.350: [CMS-concurrent-abortable-preclean-start]
13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs]
14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs]
14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
14.412: [CMS-concurrent-sweep-start]
14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs]
14.633: [CMS-concurrent-reset-start]
14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00

\n
\n
\n

在点头同意这个结论之前,让我们看看来自同一个 JVM 启动收集的垃圾收集日志的输出。显然- XX : + PrintGCDetails 告诉我们一个不同且更详细的故事:

\n
\n

基于这些信息,我们可以看到12次 Minor GC 后开始有些和上面不一样了。没有运行两次 Full GC,这不同的地方在于单个 GC 在永久代中不同阶段运行了两次:

\n
    \n
  • 最初的标记阶段,用了0.0041705秒也就是4ms左右。这个阶段会暂停“全世界( stop-the-world)”的事件,停止所有应用程序的线程,然后开始标记。
  • \n
  • 并行执行标记和清洗阶段。这些都是和应用程序线程并行的。
  • \n
  • 最后 Remark 阶段,花费了0.0462010秒约46ms。这个阶段会再次暂停所有的事件。
  • \n
  • 并行执行清理操作。正如其名,此阶段也是并行的,不会停止其他线程。
  • \n
\n

所以,正如我们从垃圾回收日志中所看到的那样,实际上只是执行了 Major GC 去清理老年代空间而已,而不是执行了两次 Full GC。

\n

如果你是后期做决 定的话,那么由 jstat 提供的数据会引导你做出正确的决策。它正确列出的两个暂停所有事件的情况,导致所有线程停止了共计50ms。但是如果你试图优化吞吐量,你会被误导的。清 单只列出了回收初始标记和最终 Remark 阶段,jstat的输出看不到那些并发完成的工作。

\n

结论

考虑到这种情况,最好避免以 Minor、Major、Full GC 这种方式来思考问题。而应该监控应用延迟或者吞吐量,然后将 GC 事件和结果联系起来。

\n

随着这些 GC 事件的发生,你需要额外的关注某些信息,GC 事件是强制所有应用程序线程停止了还是并行的处理了部分事件。

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

转载链接地址: http://www.importnew.com/15820.html

\n
","more":"

在 Plumbr 从事 GC 暂停检测相关功能的工作时,我被迫用自己的方式,通过大量文章、书籍和演讲来介绍我所做的工作。在整个过程中,经常对 Minor、Major、和 Full GC 事件的使用感到困惑。这也是我写这篇博客的原因,我希望能清楚地解释这其中的一些疑惑。

\n

文章要求读者熟悉 JVM 内置的通用垃圾回收原则。堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,代假设和其他不同的 GC 算法超出了本文讨论的范围。

\n\"图片说明\"\n

Minor GC

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:

\n

当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC。
内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
所以 Minor GC 的情况就相当清楚了——每次 Minor GC 会清理年轻代的内存。

\n

Major GC vs Full GC

大家应该注意到,目前,这些术语无论是在 JVM 规范还是在垃圾收集研究论文中都没有正式的定义。但是我们一看就知道这些在我们已经知道的基础之上做出的定义是正确的,Minor GC 清理年轻带内存应该被设计得简单:

\n

Major GC 是清理老年代。
Full GC 是清理整个堆空间—包括年轻代和老年代。
很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

\n

这使得我们不用去关心到底是叫 Major GC 还是 Full GC,大家应该关注当前的 GC 是否停止了所有应用程序的线程,还是能够并发的处理而不用停掉应用程序的线程。

\n

这种混乱甚至内置到 JVM 标准工具。下面一个例子很好的解释了我的意思。让我们比较两个不同的工具 Concurrent Mark 和 Sweep collector (-XX:+UseConcMarkSweepGC)在 JVM 中运行时输出的跟踪记录。

\n
\n
\n

第一次尝试通过 jstat 输出:

\n
\n

my-precious: me$ jstat -gc -t 4235 1s

\n

Time S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5.7 34048.0 34048.0 0.0 34048.0 272640.0 194699.7 1756416.0 181419.9 18304.0 17865.1 2688.0 2497.6 3 0.275 0 0.000 0.275
6.7 34048.0 34048.0 34048.0 0.0 272640.0 247555.4 1756416.0 263447.9 18816.0 18123.3 2688.0 2523.1 4 0.359 0 0.000 0.359
7.7 34048.0 34048.0 0.0 34048.0 272640.0 257729.3 1756416.0 345109.8 19072.0 18396.6 2688.0 2550.3 5 0.451 0 0.000 0.451
8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0 444982.5 19456.0 18681.3 2816.0 2575.8 7 0.550 0 0.000 0.550
9.7 34048.0 34048.0 34046.7 0.0 272640.0 16777.0 1756416.0 587906.3 20096.0 19235.1 2944.0 2631.8 8 0.720 0 0.000 0.720
10.7 34048.0 34048.0 0.0 34046.2 272640.0 80171.6 1756416.0 664913.4 20352.0 19495.9 2944.0 2657.4 9 0.810 0 0.000 0.810
11.7 34048.0 34048.0 34048.0 0.0 272640.0 129480.8 1756416.0 745100.2 20608.0 19704.5 2944.0 2678.4 10 0.896 0 0.000 0.896
12.7 34048.0 34048.0 0.0 34046.6 272640.0 164070.7 1756416.0 822073.7 20992.0 19937.1 3072.0 2702.8 11 0.978 0 0.000 0.978
13.7 34048.0 34048.0 34048.0 0.0 272640.0 211949.9 1756416.0 897364.4 21248.0 20179.6 3072.0 2728.1 12 1.087 1 0.004 1.091
14.7 34048.0 34048.0 0.0 34047.1 272640.0 245801.5 1756416.0 597362.6 21504.0 20390.6 3072.0 2750.3 13 1.183 2 0.050 1.233
15.7 34048.0 34048.0 0.0 34048.0 272640.0 21474.1 1756416.0 757347.0 22012.0 20792.0 3200.0 2791.0 15 1.336 2 0.050 1.386
16.7 34048.0 34048.0 34047.0 0.0 272640.0 48378.0 1756416.0 838594.4 22268.0 21003.5 3200.0 2813.2 16 1.433 2 0.050 1.484

\n
\n

这个片段是 JVM 启动后第17秒提取的。基于该信息,我们可以得出这样的结果,运行了12次 Minor GC、2次 Full GC,时间总跨度为50毫秒。通过 jconsole 或者 jvisualvm 这样的基于GUI的工具你能得到同样的结果。

\n
\n

java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer

\n

3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs]
… cut for brevity …
11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs]
12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs]
12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs]
13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
13.102: [CMS-concurrent-mark-start]
13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs]
13.341: [CMS-concurrent-preclean-start]
13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
13.350: [CMS-concurrent-abortable-preclean-start]
13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs]
14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs]
14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
14.412: [CMS-concurrent-sweep-start]
14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs]
14.633: [CMS-concurrent-reset-start]
14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00

\n
\n
\n

在点头同意这个结论之前,让我们看看来自同一个 JVM 启动收集的垃圾收集日志的输出。显然- XX : + PrintGCDetails 告诉我们一个不同且更详细的故事:

\n
\n

基于这些信息,我们可以看到12次 Minor GC 后开始有些和上面不一样了。没有运行两次 Full GC,这不同的地方在于单个 GC 在永久代中不同阶段运行了两次:

\n
    \n
  • 最初的标记阶段,用了0.0041705秒也就是4ms左右。这个阶段会暂停“全世界( stop-the-world)”的事件,停止所有应用程序的线程,然后开始标记。
  • \n
  • 并行执行标记和清洗阶段。这些都是和应用程序线程并行的。
  • \n
  • 最后 Remark 阶段,花费了0.0462010秒约46ms。这个阶段会再次暂停所有的事件。
  • \n
  • 并行执行清理操作。正如其名,此阶段也是并行的,不会停止其他线程。
  • \n
\n

所以,正如我们从垃圾回收日志中所看到的那样,实际上只是执行了 Major GC 去清理老年代空间而已,而不是执行了两次 Full GC。

\n

如果你是后期做决 定的话,那么由 jstat 提供的数据会引导你做出正确的决策。它正确列出的两个暂停所有事件的情况,导致所有线程停止了共计50ms。但是如果你试图优化吞吐量,你会被误导的。清 单只列出了回收初始标记和最终 Remark 阶段,jstat的输出看不到那些并发完成的工作。

\n

结论

考虑到这种情况,最好避免以 Minor、Major、Full GC 这种方式来思考问题。而应该监控应用延迟或者吞吐量,然后将 GC 事件和结果联系起来。

\n

随着这些 GC 事件的发生,你需要额外的关注某些信息,GC 事件是强制所有应用程序线程停止了还是并行的处理了部分事件。

\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"Python 爬虫实战(2)","comments":1,"description":"第二章相较于第一章, 加入了字典的应用,文件读写,也加深了对爬虫的了解,了解到Robbts协议(个人百度的);另外链接中的代码有的我本地并不能运行成功,所以加入了写自己的写法。","toc":true,"_content":"\n## 目标: 获取上交所和深交所所有股票的名称和交易信息,存储到一个本地文件中\n\n> 网站选择原则: 股票信息静态存在于html页面中,非js代码生成,没有Robbts协议限制\n\n> 选取方法: 打开网页,查看源代码,搜索网页的股票价格数据是否存在于源代码中\n\n\n\n* 下面的百度股市通中,股票的信息完全再html代码中,符合要求(并且发现网址中包含我们需要的关键字:sz代表深交所,而后面的数字就是股票代码了)\n\n{% qnimg 20171226092624.png title:百度股市通 alt:输出结果 extend:?imageView2/2/w/600 %}\n{% qnimg 20171226092340.png title:百度股市通 alt:输出结果 extend:?imageView2/2/w/600 %}\n\n* 除了单个股票的信息,我们需要所有交所和深交所的股票,访问 http://quote.eastmoney.com/stocklist.html 查看页面\n\n{% qnimg 20171226093006.png title:可获得所有的股票代码 alt:股票代码 extend:?imageView2/2/w/600 %}\n\n* 所有我们只需要先获取所有的股票代码,然后循环访问百度即可获得所有的股票信息\n\n> 输出结果: \n\n{% qnimg 20171226091742.png title:输出结果 alt:输出结果 extend:?imageView2/2/w/600 %}\n\n**大部分讲解都在 Python 爬虫实战(1) 中介绍过了,需要请查看 http://zj2626.github.io/2017/12/14/20171214_crawler**\n\n### python文件读写\n\n* Python内置了读写文件的函数,用法和C是兼容的。\n\n*在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。*\n\n> 读文件\n\n* python内置的open()函数,返回一个文件对象;(参数中 r代表读 w代表写)\n\n{% qnimg 20171226094739.png title:读文件 alt:读文件 extend:?imageView2/2/w/600 %}\n\n* 得到文件对象,则可以直接调用f.read()把文件内容读取到内存中来\n```python\nf.read()\n```\n\n读取时发生的问题:\n1. 如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在\n2. 'gbk' codec can't decode byte 0xaf in position ...\n\n```python\n#问题2解决方案两个:\n# 1. 打开文件的时候就指定编码的类型\n f = open('E:/Data.txt', 'r',encoding = 'utf-8')\n f.read()\n# 2. 修改文件编码为utf-8\n```\n\n* 最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的\n\n```python\nf.close()\n```\n\n* 为了防止中途出现异常而无法关闭文件,使用try finally语句\n\n```python\ntry:\n f = open('E:/Data.txt', 'r', encoding = 'utf-8')\n print(f.read())\nfinally:\n if f:\n f.close()\n```\n\n* 为了简化代码,python提供了一个更好的更简洁的方法读取文件(和try...finally一样的)\n\n```python\nwith open('E:/Data.txt', 'r', encoding = 'utf-8') as f:\n print(f.read())\n```\n\n* read()方法一次把所有的文件内容读取进来,如果文件太大就不太好用,所以要反复调用read(size)来一部分一部分的读取,也可以调用readline()一次读取一行,或者调用readlines()一次读取全部并返回list\n```python\nwith open('E:/Data.txt', 'r', encoding = 'utf-8') as f:\n print(f.readline())\n print(f.readline())\n print(f.readline())\n```\n*如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便*\n\n> 写文件\n\n* 第二个参数传入标识符'w'或者'wb'表示写文本文件或写二进制文件; write()函数会把数据替换掉原文件中内容\n```python\ntry:\n f = open('E:/Data.txt', 'w', encoding = 'utf-8')\n f.write('ffffffffffffffffffffffff')\nfinally:\n if f:\n f.close()\n```\n*当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。*\n\n* 同读取一样 系统提供更好的\n```python\nwith open('E:/Data.txt', 'w', encoding = 'utf-8') as f:\n f.write('kkkkkkkkkkkkkkkk')\n```\n\n> 二进制文件\n\n* 前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可\n\n```python\nwith open('D:/20171226101748.png', 'rb') as f:\n f.read()\n```\n\n## 个人代码:\n\n```python\nfrom urllib import request\nfrom bs4 import BeautifulSoup as bs\nimport re\n\ndef getCodes():\n url = 'http://quote.eastmoney.com/stocklist.html';\n resp = request.urlopen(url)\n resp_text = resp.read().decode('gbk')\n soap = bs(resp_text, 'html.parser')\n list = soap.find_all('div', id = 'quotesearch')[0].find_all('ul')[0].find_all('li')\n\n codeList = []\n for li in list:\n try: \n #eg: sh603183\n codeList.append(re.findall(r\"[s][hz]\\d{6}\", li.find('a')['href']))\n except:\n continue\n \n return codeList\n\ndef makeDict(code):\n infoDict = {}\n url = 'https://gupiao.baidu.com/stock/'+ code +'.html';\n resp = request.urlopen(url)\n resp_text = resp.read().decode('utf-8')\n soap = bs(resp_text, 'html.parser')\n \n try:\n stockInfo = soap.find_all(attrs = {'class','stock-bets'})\n name = soap.find(attrs = {'class', 'bets-name'})\n if name is None:\n return None\n infoDict['name'] = name.text.strip()\n keys = soap.find_all('dt')\n values = soap.find_all('dd')\n for i in range(len(keys)):\n infoDict[keys[i].text] = values[i].text\n\n return infoDict\n except:\n return None\n\ndef writeFile(codeList):\n i=0\n for code in codeList:\n i = i+1\n # 下面两个判断是因为前45个股票百度并没有信息,所以跳过了,200个以后的数据就不再取了,太多了,科科\n if i < 45:\n continue\n if i > 200:\n break\n infoDict = makeDict(code[0])\n if infoDict is None:\n continue\n print (infoDict)\n with open('E://Data.txt', 'a', encoding = 'utf-8') as f:\n f.write(str(infoDict) + '\\n')\n\ncodeList = getCodes();\nprint(\"start\")\nwriteFile(codeList)\n```\n\n## 别人家的代码【滑稽】:\n\n```python\n# -*- coding: utf-8 -*-\n \nimport requests\nfrom bs4 import BeautifulSoup\nimport traceback\nimport re\n \ndef getHTMLText(url):\n try:\n r = requests.get(url)\n r.raise_for_status()\n r.encoding = r.apparent_encoding\n return r.text\n except:\n return \"\"\n \ndef getStockList(lst, stockURL):\n html = getHTMLText(stockURL)\n soup = BeautifulSoup(html, 'html.parser') \n a = soup.find_all('a')\n for i in a:\n try:\n href = i.attrs['href']\n lst.append(re.findall(r\"[s][hz]\\d{6}\", href)[0])\n except:\n continue\n \ndef getStockInfo(lst, stockURL, fpath):\n count = 0\n for stock in lst:\n url = stockURL + stock + \".html\"\n html = getHTMLText(url)\n try:\n if html==\"\":\n continue\n infoDict = {}\n soup = BeautifulSoup(html, 'html.parser')\n stockInfo = soup.find('div',attrs={'class':'stock-bets'})\n \n name = stockInfo.find_all(attrs={'class':'bets-name'})[0]\n infoDict.update({'股票名称': name.text.split()[0]})\n \n keyList = stockInfo.find_all('dt')\n valueList = stockInfo.find_all('dd')\n for i in range(len(keyList)):\n key = keyList[i].text\n val = valueList[i].text\n infoDict[key] = val\n \n with open(fpath, 'a', encoding='utf-8') as f:\n f.write( str(infoDict) + '\\n' )\n count = count + 1\n print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")\n except:\n count = count + 1\n print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")\n continue\n \ndef main():\n stock_list_url = 'http://quote.eastmoney.com/stocklist.html'\n stock_info_url = 'https://gupiao.baidu.com/stock/'\n output_file = 'D:/BaiduStockInfo.txt'\n slist=[]\n getStockList(slist, stock_list_url)\n getStockInfo(slist, stock_info_url, output_file)\n \nmain()\n```\n> 转载自 链接地址: http://python.jobbole.com/88350/\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20171219_crawler2.md","raw":"---\ntitle: Python 爬虫实战(2)\n\ncomments: true \n\ntags: \n - python\n - 爬虫\n\ncategories: \n - 爬虫\n\ndescription: 第二章相较于第一章, 加入了字典的应用,文件读写,也加深了对爬虫的了解,了解到Robbts协议(个人百度的);另外链接中的代码有的我本地并不能运行成功,所以加入了写自己的写法。\n\ntoc: true\n \n---\n\n## 目标: 获取上交所和深交所所有股票的名称和交易信息,存储到一个本地文件中\n\n> 网站选择原则: 股票信息静态存在于html页面中,非js代码生成,没有Robbts协议限制\n\n> 选取方法: 打开网页,查看源代码,搜索网页的股票价格数据是否存在于源代码中\n\n\n\n* 下面的百度股市通中,股票的信息完全再html代码中,符合要求(并且发现网址中包含我们需要的关键字:sz代表深交所,而后面的数字就是股票代码了)\n\n{% qnimg 20171226092624.png title:百度股市通 alt:输出结果 extend:?imageView2/2/w/600 %}\n{% qnimg 20171226092340.png title:百度股市通 alt:输出结果 extend:?imageView2/2/w/600 %}\n\n* 除了单个股票的信息,我们需要所有交所和深交所的股票,访问 http://quote.eastmoney.com/stocklist.html 查看页面\n\n{% qnimg 20171226093006.png title:可获得所有的股票代码 alt:股票代码 extend:?imageView2/2/w/600 %}\n\n* 所有我们只需要先获取所有的股票代码,然后循环访问百度即可获得所有的股票信息\n\n> 输出结果: \n\n{% qnimg 20171226091742.png title:输出结果 alt:输出结果 extend:?imageView2/2/w/600 %}\n\n**大部分讲解都在 Python 爬虫实战(1) 中介绍过了,需要请查看 http://zj2626.github.io/2017/12/14/20171214_crawler**\n\n### python文件读写\n\n* Python内置了读写文件的函数,用法和C是兼容的。\n\n*在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。*\n\n> 读文件\n\n* python内置的open()函数,返回一个文件对象;(参数中 r代表读 w代表写)\n\n{% qnimg 20171226094739.png title:读文件 alt:读文件 extend:?imageView2/2/w/600 %}\n\n* 得到文件对象,则可以直接调用f.read()把文件内容读取到内存中来\n```python\nf.read()\n```\n\n读取时发生的问题:\n1. 如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在\n2. 'gbk' codec can't decode byte 0xaf in position ...\n\n```python\n#问题2解决方案两个:\n# 1. 打开文件的时候就指定编码的类型\n f = open('E:/Data.txt', 'r',encoding = 'utf-8')\n f.read()\n# 2. 修改文件编码为utf-8\n```\n\n* 最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的\n\n```python\nf.close()\n```\n\n* 为了防止中途出现异常而无法关闭文件,使用try finally语句\n\n```python\ntry:\n f = open('E:/Data.txt', 'r', encoding = 'utf-8')\n print(f.read())\nfinally:\n if f:\n f.close()\n```\n\n* 为了简化代码,python提供了一个更好的更简洁的方法读取文件(和try...finally一样的)\n\n```python\nwith open('E:/Data.txt', 'r', encoding = 'utf-8') as f:\n print(f.read())\n```\n\n* read()方法一次把所有的文件内容读取进来,如果文件太大就不太好用,所以要反复调用read(size)来一部分一部分的读取,也可以调用readline()一次读取一行,或者调用readlines()一次读取全部并返回list\n```python\nwith open('E:/Data.txt', 'r', encoding = 'utf-8') as f:\n print(f.readline())\n print(f.readline())\n print(f.readline())\n```\n*如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便*\n\n> 写文件\n\n* 第二个参数传入标识符'w'或者'wb'表示写文本文件或写二进制文件; write()函数会把数据替换掉原文件中内容\n```python\ntry:\n f = open('E:/Data.txt', 'w', encoding = 'utf-8')\n f.write('ffffffffffffffffffffffff')\nfinally:\n if f:\n f.close()\n```\n*当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。*\n\n* 同读取一样 系统提供更好的\n```python\nwith open('E:/Data.txt', 'w', encoding = 'utf-8') as f:\n f.write('kkkkkkkkkkkkkkkk')\n```\n\n> 二进制文件\n\n* 前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可\n\n```python\nwith open('D:/20171226101748.png', 'rb') as f:\n f.read()\n```\n\n## 个人代码:\n\n```python\nfrom urllib import request\nfrom bs4 import BeautifulSoup as bs\nimport re\n\ndef getCodes():\n url = 'http://quote.eastmoney.com/stocklist.html';\n resp = request.urlopen(url)\n resp_text = resp.read().decode('gbk')\n soap = bs(resp_text, 'html.parser')\n list = soap.find_all('div', id = 'quotesearch')[0].find_all('ul')[0].find_all('li')\n\n codeList = []\n for li in list:\n try: \n #eg: sh603183\n codeList.append(re.findall(r\"[s][hz]\\d{6}\", li.find('a')['href']))\n except:\n continue\n \n return codeList\n\ndef makeDict(code):\n infoDict = {}\n url = 'https://gupiao.baidu.com/stock/'+ code +'.html';\n resp = request.urlopen(url)\n resp_text = resp.read().decode('utf-8')\n soap = bs(resp_text, 'html.parser')\n \n try:\n stockInfo = soap.find_all(attrs = {'class','stock-bets'})\n name = soap.find(attrs = {'class', 'bets-name'})\n if name is None:\n return None\n infoDict['name'] = name.text.strip()\n keys = soap.find_all('dt')\n values = soap.find_all('dd')\n for i in range(len(keys)):\n infoDict[keys[i].text] = values[i].text\n\n return infoDict\n except:\n return None\n\ndef writeFile(codeList):\n i=0\n for code in codeList:\n i = i+1\n # 下面两个判断是因为前45个股票百度并没有信息,所以跳过了,200个以后的数据就不再取了,太多了,科科\n if i < 45:\n continue\n if i > 200:\n break\n infoDict = makeDict(code[0])\n if infoDict is None:\n continue\n print (infoDict)\n with open('E://Data.txt', 'a', encoding = 'utf-8') as f:\n f.write(str(infoDict) + '\\n')\n\ncodeList = getCodes();\nprint(\"start\")\nwriteFile(codeList)\n```\n\n## 别人家的代码【滑稽】:\n\n```python\n# -*- coding: utf-8 -*-\n \nimport requests\nfrom bs4 import BeautifulSoup\nimport traceback\nimport re\n \ndef getHTMLText(url):\n try:\n r = requests.get(url)\n r.raise_for_status()\n r.encoding = r.apparent_encoding\n return r.text\n except:\n return \"\"\n \ndef getStockList(lst, stockURL):\n html = getHTMLText(stockURL)\n soup = BeautifulSoup(html, 'html.parser') \n a = soup.find_all('a')\n for i in a:\n try:\n href = i.attrs['href']\n lst.append(re.findall(r\"[s][hz]\\d{6}\", href)[0])\n except:\n continue\n \ndef getStockInfo(lst, stockURL, fpath):\n count = 0\n for stock in lst:\n url = stockURL + stock + \".html\"\n html = getHTMLText(url)\n try:\n if html==\"\":\n continue\n infoDict = {}\n soup = BeautifulSoup(html, 'html.parser')\n stockInfo = soup.find('div',attrs={'class':'stock-bets'})\n \n name = stockInfo.find_all(attrs={'class':'bets-name'})[0]\n infoDict.update({'股票名称': name.text.split()[0]})\n \n keyList = stockInfo.find_all('dt')\n valueList = stockInfo.find_all('dd')\n for i in range(len(keyList)):\n key = keyList[i].text\n val = valueList[i].text\n infoDict[key] = val\n \n with open(fpath, 'a', encoding='utf-8') as f:\n f.write( str(infoDict) + '\\n' )\n count = count + 1\n print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")\n except:\n count = count + 1\n print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")\n continue\n \ndef main():\n stock_list_url = 'http://quote.eastmoney.com/stocklist.html'\n stock_info_url = 'https://gupiao.baidu.com/stock/'\n output_file = 'D:/BaiduStockInfo.txt'\n slist=[]\n getStockList(slist, stock_list_url)\n getStockInfo(slist, stock_info_url, output_file)\n \nmain()\n```\n> 转载自 链接地址: http://python.jobbole.com/88350/\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20171219_crawler2","published":1,"date":"2020-01-15T05:50:39.646Z","updated":"2021-03-10T13:50:15.361Z","layout":"post","photos":[],"link":"","_id":"ckm3inve5007t24ujkrhd238v","content":"

目标: 获取上交所和深交所所有股票的名称和交易信息,存储到一个本地文件中

\n

网站选择原则: 股票信息静态存在于html页面中,非js代码生成,没有Robbts协议限制

\n
\n
\n

选取方法: 打开网页,查看源代码,搜索网页的股票价格数据是否存在于源代码中

\n
\n\n
    \n
  • 下面的百度股市通中,股票的信息完全再html代码中,符合要求(并且发现网址中包含我们需要的关键字:sz代表深交所,而后面的数字就是股票代码了)
  • \n
\n\"输出结果\"\n\"输出结果\"\n\n\"股票代码\"\n
    \n
  • 所有我们只需要先获取所有的股票代码,然后循环访问百度即可获得所有的股票信息
  • \n
\n
\n

输出结果:

\n
\n\"输出结果\"\n

大部分讲解都在 Python 爬虫实战(1) 中介绍过了,需要请查看 http://zj2626.github.io/2017/12/14/20171214_crawler

\n

python文件读写

    \n
  • Python内置了读写文件的函数,用法和C是兼容的。
  • \n
\n

在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

\n
\n

读文件

\n
\n
    \n
  • python内置的open()函数,返回一个文件对象;(参数中 r代表读 w代表写)
  • \n
\n\"读文件\"\n
    \n
  • 得到文件对象,则可以直接调用f.read()把文件内容读取到内存中来
    1
    f.read()
    \n
  • \n
\n

读取时发生的问题:

\n
    \n
  1. 如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在
  2. \n
  3. ‘gbk’ codec can’t decode byte 0xaf in position …
  4. \n
\n
1
2
3
4
5
#问题2解决方案两个:
# 1. 打开文件的时候就指定编码的类型
f = open('E:/Data.txt', 'r',encoding = 'utf-8')
f.read()
# 2. 修改文件编码为utf-8
\n
    \n
  • 最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的
  • \n
\n
1
f.close()
\n
    \n
  • 为了防止中途出现异常而无法关闭文件,使用try finally语句
  • \n
\n
1
2
3
4
5
6
try:
f = open('E:/Data.txt', 'r', encoding = 'utf-8')
print(f.read())
finally:
if f:
f.close()
\n
    \n
  • 为了简化代码,python提供了一个更好的更简洁的方法读取文件(和try…finally一样的)
  • \n
\n
1
2
with open('E:/Data.txt', 'r', encoding = 'utf-8') as f:
print(f.read())
\n
    \n
  • read()方法一次把所有的文件内容读取进来,如果文件太大就不太好用,所以要反复调用read(size)来一部分一部分的读取,也可以调用readline()一次读取一行,或者调用readlines()一次读取全部并返回list
    1
    2
    3
    4
    with open('E:/Data.txt', 'r', encoding = 'utf-8') as f:
    print(f.readline())
    print(f.readline())
    print(f.readline())
    \n
  • \n
\n

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便

\n
\n

写文件

\n
\n
    \n
  • 第二个参数传入标识符’w’或者’wb’表示写文本文件或写二进制文件; write()函数会把数据替换掉原文件中内容
    1
    2
    3
    4
    5
    6
    try:
    f = open('E:/Data.txt', 'w', encoding = 'utf-8')
    f.write('ffffffffffffffffffffffff')
    finally:
    if f:
    f.close()
    \n
  • \n
\n

当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。

\n
    \n
  • 同读取一样 系统提供更好的
    1
    2
    with open('E:/Data.txt', 'w', encoding = 'utf-8') as f:
    f.write('kkkkkkkkkkkkkkkk')
    \n
  • \n
\n
\n

二进制文件

\n
\n
    \n
  • 前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用’rb’模式打开文件即可
  • \n
\n
1
2
with open('D:/20171226101748.png', 'rb') as f:
f.read()
\n

个人代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from urllib import request
from bs4 import BeautifulSoup as bs
import re

def getCodes():
url = 'http://quote.eastmoney.com/stocklist.html';
resp = request.urlopen(url)
resp_text = resp.read().decode('gbk')
soap = bs(resp_text, 'html.parser')
list = soap.find_all('div', id = 'quotesearch')[0].find_all('ul')[0].find_all('li')

codeList = []
for li in list:
try:
#eg: sh603183
codeList.append(re.findall(r\"[s][hz]\\d{6}\", li.find('a')['href']))
except:
continue

return codeList

def makeDict(code):
infoDict = {}
url = 'https://gupiao.baidu.com/stock/'+ code +'.html';
resp = request.urlopen(url)
resp_text = resp.read().decode('utf-8')
soap = bs(resp_text, 'html.parser')

try:
stockInfo = soap.find_all(attrs = {'class','stock-bets'})
name = soap.find(attrs = {'class', 'bets-name'})
if name is None:
return None
infoDict['name'] = name.text.strip()
keys = soap.find_all('dt')
values = soap.find_all('dd')
for i in range(len(keys)):
infoDict[keys[i].text] = values[i].text

return infoDict
except:
return None

def writeFile(codeList):
i=0
for code in codeList:
i = i+1
# 下面两个判断是因为前45个股票百度并没有信息,所以跳过了,200个以后的数据就不再取了,太多了,科科
if i < 45:
continue
if i > 200:
break
infoDict = makeDict(code[0])
if infoDict is None:
continue
print (infoDict)
with open('E://Data.txt', 'a', encoding = 'utf-8') as f:
f.write(str(infoDict) + '\\n')

codeList = getCodes();
print(\"start\")
writeFile(codeList)
\n

别人家的代码【滑稽】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# -*- coding: utf-8 -*-

import requests
from bs4 import BeautifulSoup
import traceback
import re

def getHTMLText(url):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return \"\"

def getStockList(lst, stockURL):
html = getHTMLText(stockURL)
soup = BeautifulSoup(html, 'html.parser')
a = soup.find_all('a')
for i in a:
try:
href = i.attrs['href']
lst.append(re.findall(r\"[s][hz]\\d{6}\", href)[0])
except:
continue

def getStockInfo(lst, stockURL, fpath):
count = 0
for stock in lst:
url = stockURL + stock + \".html\"
html = getHTMLText(url)
try:
if html==\"\":
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div',attrs={'class':'stock-bets'})

name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
infoDict.update({'股票名称': name.text.split()[0]})

keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
infoDict[key] = val

with open(fpath, 'a', encoding='utf-8') as f:
f.write( str(infoDict) + '\\n' )
count = count + 1
print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")
except:
count = count + 1
print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")
continue

def main():
stock_list_url = 'http://quote.eastmoney.com/stocklist.html'
stock_info_url = 'https://gupiao.baidu.com/stock/'
output_file = 'D:/BaiduStockInfo.txt'
slist=[]
getStockList(slist, stock_list_url)
getStockInfo(slist, stock_info_url, output_file)

main()
\n
\n

转载自 链接地址: http://python.jobbole.com/88350/

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"

目标: 获取上交所和深交所所有股票的名称和交易信息,存储到一个本地文件中

\n

网站选择原则: 股票信息静态存在于html页面中,非js代码生成,没有Robbts协议限制

\n
\n
\n

选取方法: 打开网页,查看源代码,搜索网页的股票价格数据是否存在于源代码中

\n
","more":"
    \n
  • 下面的百度股市通中,股票的信息完全再html代码中,符合要求(并且发现网址中包含我们需要的关键字:sz代表深交所,而后面的数字就是股票代码了)
  • \n
\n\"输出结果\"\n\"输出结果\"\n\n\"股票代码\"\n
    \n
  • 所有我们只需要先获取所有的股票代码,然后循环访问百度即可获得所有的股票信息
  • \n
\n
\n

输出结果:

\n
\n\"输出结果\"\n

大部分讲解都在 Python 爬虫实战(1) 中介绍过了,需要请查看 http://zj2626.github.io/2017/12/14/20171214_crawler

\n

python文件读写

    \n
  • Python内置了读写文件的函数,用法和C是兼容的。
  • \n
\n

在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

\n
\n

读文件

\n
\n
    \n
  • python内置的open()函数,返回一个文件对象;(参数中 r代表读 w代表写)
  • \n
\n\"读文件\"\n
    \n
  • 得到文件对象,则可以直接调用f.read()把文件内容读取到内存中来
    1
    f.read()
    \n
  • \n
\n

读取时发生的问题:

\n
    \n
  1. 如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在
  2. \n
  3. ‘gbk’ codec can’t decode byte 0xaf in position …
  4. \n
\n
1
2
3
4
5
#问题2解决方案两个:
# 1. 打开文件的时候就指定编码的类型
f = open('E:/Data.txt', 'r',encoding = 'utf-8')
f.read()
# 2. 修改文件编码为utf-8
\n
    \n
  • 最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的
  • \n
\n
1
f.close()
\n
    \n
  • 为了防止中途出现异常而无法关闭文件,使用try finally语句
  • \n
\n
1
2
3
4
5
6
try:
f = open('E:/Data.txt', 'r', encoding = 'utf-8')
print(f.read())
finally:
if f:
f.close()
\n
    \n
  • 为了简化代码,python提供了一个更好的更简洁的方法读取文件(和try…finally一样的)
  • \n
\n
1
2
with open('E:/Data.txt', 'r', encoding = 'utf-8') as f:
print(f.read())
\n
    \n
  • read()方法一次把所有的文件内容读取进来,如果文件太大就不太好用,所以要反复调用read(size)来一部分一部分的读取,也可以调用readline()一次读取一行,或者调用readlines()一次读取全部并返回list
    1
    2
    3
    4
    with open('E:/Data.txt', 'r', encoding = 'utf-8') as f:
    print(f.readline())
    print(f.readline())
    print(f.readline())
    \n
  • \n
\n

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便

\n
\n

写文件

\n
\n
    \n
  • 第二个参数传入标识符’w’或者’wb’表示写文本文件或写二进制文件; write()函数会把数据替换掉原文件中内容
    1
    2
    3
    4
    5
    6
    try:
    f = open('E:/Data.txt', 'w', encoding = 'utf-8')
    f.write('ffffffffffffffffffffffff')
    finally:
    if f:
    f.close()
    \n
  • \n
\n

当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。

\n
    \n
  • 同读取一样 系统提供更好的
    1
    2
    with open('E:/Data.txt', 'w', encoding = 'utf-8') as f:
    f.write('kkkkkkkkkkkkkkkk')
    \n
  • \n
\n
\n

二进制文件

\n
\n
    \n
  • 前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用’rb’模式打开文件即可
  • \n
\n
1
2
with open('D:/20171226101748.png', 'rb') as f:
f.read()
\n

个人代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from urllib import request
from bs4 import BeautifulSoup as bs
import re

def getCodes():
url = 'http://quote.eastmoney.com/stocklist.html';
resp = request.urlopen(url)
resp_text = resp.read().decode('gbk')
soap = bs(resp_text, 'html.parser')
list = soap.find_all('div', id = 'quotesearch')[0].find_all('ul')[0].find_all('li')

codeList = []
for li in list:
try:
#eg: sh603183
codeList.append(re.findall(r\"[s][hz]\\d{6}\", li.find('a')['href']))
except:
continue

return codeList

def makeDict(code):
infoDict = {}
url = 'https://gupiao.baidu.com/stock/'+ code +'.html';
resp = request.urlopen(url)
resp_text = resp.read().decode('utf-8')
soap = bs(resp_text, 'html.parser')

try:
stockInfo = soap.find_all(attrs = {'class','stock-bets'})
name = soap.find(attrs = {'class', 'bets-name'})
if name is None:
return None
infoDict['name'] = name.text.strip()
keys = soap.find_all('dt')
values = soap.find_all('dd')
for i in range(len(keys)):
infoDict[keys[i].text] = values[i].text

return infoDict
except:
return None

def writeFile(codeList):
i=0
for code in codeList:
i = i+1
# 下面两个判断是因为前45个股票百度并没有信息,所以跳过了,200个以后的数据就不再取了,太多了,科科
if i < 45:
continue
if i > 200:
break
infoDict = makeDict(code[0])
if infoDict is None:
continue
print (infoDict)
with open('E://Data.txt', 'a', encoding = 'utf-8') as f:
f.write(str(infoDict) + '\\n')

codeList = getCodes();
print(\"start\")
writeFile(codeList)
\n

别人家的代码【滑稽】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# -*- coding: utf-8 -*-

import requests
from bs4 import BeautifulSoup
import traceback
import re

def getHTMLText(url):
try:
r = requests.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return \"\"

def getStockList(lst, stockURL):
html = getHTMLText(stockURL)
soup = BeautifulSoup(html, 'html.parser')
a = soup.find_all('a')
for i in a:
try:
href = i.attrs['href']
lst.append(re.findall(r\"[s][hz]\\d{6}\", href)[0])
except:
continue

def getStockInfo(lst, stockURL, fpath):
count = 0
for stock in lst:
url = stockURL + stock + \".html\"
html = getHTMLText(url)
try:
if html==\"\":
continue
infoDict = {}
soup = BeautifulSoup(html, 'html.parser')
stockInfo = soup.find('div',attrs={'class':'stock-bets'})

name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
infoDict.update({'股票名称': name.text.split()[0]})

keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
infoDict[key] = val

with open(fpath, 'a', encoding='utf-8') as f:
f.write( str(infoDict) + '\\n' )
count = count + 1
print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")
except:
count = count + 1
print(\"\\r当前进度: {:.2f}%\".format(count*100/len(lst)),end=\"\")
continue

def main():
stock_list_url = 'http://quote.eastmoney.com/stocklist.html'
stock_info_url = 'https://gupiao.baidu.com/stock/'
output_file = 'D:/BaiduStockInfo.txt'
slist=[]
getStockList(slist, stock_list_url)
getStockInfo(slist, stock_info_url, output_file)

main()
\n
\n

转载自 链接地址: http://python.jobbole.com/88350/

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"Python 爬虫实战(3)","comments":1,"description":null,"toc":true,"_content":"\n> Socket 网络编程\n\nSocket(套接字),是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信, 又称进程间通信, IP协议进行的主要是端到端通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。\n\n所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程\n\n端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023, 用户自定义端口号一般大于等于1024\n\n每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的\n\n\n\n> 原文见: http://python.jobbole.com/88396/\n\n## 个人代码:\n\n### TCP\n\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket #socket模块\n \nBUF_SIZE = 1024 #设置缓冲区大小\nserver_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址\ntry :\n server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象\nexcept socket.error as msg :\n print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])\n sys.exit()\nprint (\"Socket Created!\")\nserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用\ntry : \n server.bind(server_addr) #绑定地址\nexcept socket.error as msg :\n print (\"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])\n sys.exit()\nprint (\"Socket Bind!\")\nserver.listen(5) #监听, 最大监听数为5\nprint (\"Socket listening\")\nwhile True:\n client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数\n print ('Connected by', client_addr)\n while True :\n data = client.recv(BUF_SIZE) #从客户端接收数据\n print (str(data, encoding = \"utf-8\"))\n if data == b'exit':\n break\n client.sendall(data) #发送数据到客户端\nserver.close()\n\n\n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket\n \nBUF_SIZE = 1024 #设置缓冲区的大小\nserver_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址\ntry : \n client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象\nexcept socket.error as msg :\n print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])\n sys.exit()\nclient.connect(server_addr) #要连接的服务器地址\nwhile True:\n data = input(\"Please input some string > \") \n if not data :\n print (\"input can't empty, Please input again..\")\n continue\n client.sendall(bytes(data, encoding = 'utf-8')) #发送数据到服务器\n # client.sendall(str.encode(data)) \n data = client.recv(BUF_SIZE) #从服务器端接收数据\n print (str(data, encoding = \"utf-8\"))\nclient.close()\n```\n\n### UDP\n\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n\nimport socket #socket模块\nBUFF_SIZE = 1024\nserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\naddress = ('127.0.0.1', 12346)\nserver.bind(address)\n\nwhile True:\n print (\"WAIT\")\n data, client_address = server.recvfrom(BUFF_SIZE)\n print (str(data, encoding='utf-8'), 'from' ,client_address)\n server.sendto(data, client_address)\n \n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n\nimport socket #socket模块\nimport sys\nBUFF_SIZE = 1024\nclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\naddress = ('127.0.0.1', 12346)\n\nwhile True:\n data = input(\"Please input some \") \n if data == 'exit':\n break\n client.sendto(bytes(data, encoding='utf-8'), address)\n data = client.recv(BUFF_SIZE)\n print (str(data, encoding='utf-8')) \n```\n\n\n## 别人家的代码【滑稽】:\n\n### TCP\n\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket #socket模块\n \nBUF_SIZE = 1024 #设置缓冲区大小\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\ntry :\n server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象\nexcept socket.error, msg :\n print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]\n sys.exit()\nprint \"Socket Created!\"\nserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用\ntry : \n server.bind(server_addr) #绑定地址\nexcept socket.error, msg :\n print \"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]\n sys.exit()\nprint \"Socket Bind!\"\nserver.listen(5) #监听, 最大监听数为5\nprint \"Socket listening\"\nwhile True:\n client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数\n print 'Connected by', client_addr\n while True :\n data = client.recv(BUF_SIZE) #从客户端接收数据\n print data\n client.sendall(data) #发送数据到客户端\nserver.close()\n\n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket\n \n \nBUF_SIZE = 1024 #设置缓冲区的大小\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\ntry : \n client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象\nexcept socket.error, msg :\n print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]\n sys.exit()\nclient.connect(server_addr) #要连接的服务器地址\nwhile True:\n data = raw_input(\"Please input some string > \") \n if not data :\n print \"input can't empty, Please input again..\"\n continue\n client.sendall(data) #发送数据到服务器\n data = client.recv(BUF_SIZE) #从服务器端接收数据\n print data\nclient.close()\n```\n\n### UDP\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport socket\n \nBUF_SIZE = 1024 #设置缓冲区大小\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\nserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象\nserver.bind(server_addr) #套接字绑定IP和端口\nwhile True :\n print \"waitting for data\"\n data, client_addr = server.recvfrom(BUF_SIZE) #从客户端接收数据\n print 'Connected by', client_addr, ' Receive Data : ', data\n server.sendto(data, client_addr) #发送数据给客户端\nserver.close()\n\n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport socket\nimport struct\n \nBUF_SIZE = 1024 #设置缓冲区\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\nclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象\n \nwhile True :\n data = raw_input('Please Input data > ')\n client.sendto(data, server_addr) #向服务器发送数据\n data, addr = client.recvfrom(BUF_SIZE) #从服务器接收数据\n print \"Data : \", data\nclient.close()\n```\n\n### 其他\n\n```python\ns.getpeername()\n#返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。\n \ns.getsockname()\n#返回套接字自己的地址。通常是一个元组(ipaddr,port)\n \ns.setsockopt(level,optname,value)\n#设置给定套接字选项的值。\n \ns.getsockopt(level,optname[.buflen])\n#返回套接字选项的值。\n \ns.settimeout(timeout)\n#设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())\n \ns.gettimeout()\n#返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。\n \ns.fileno()\n#返回套接字的文件描述符。\n \ns.setblocking(flag)\n#如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。\n \ns.makefile()\n#创建一个与该套接字相关连的文件\n```\n\n> 转载自 链接地址: http://python.jobbole.com/88396/\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20171219_crawler3.md","raw":"---\ntitle: Python 爬虫实战(3)\n\ncomments: true \n\ntags: \n - python\n - 爬虫\n\ncategories: \n - 爬虫\n\ndescription:\n\ntoc: true\n \n---\n\n> Socket 网络编程\n\nSocket(套接字),是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信, 又称进程间通信, IP协议进行的主要是端到端通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。\n\n所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程\n\n端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023, 用户自定义端口号一般大于等于1024\n\n每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的\n\n\n\n> 原文见: http://python.jobbole.com/88396/\n\n## 个人代码:\n\n### TCP\n\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket #socket模块\n \nBUF_SIZE = 1024 #设置缓冲区大小\nserver_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址\ntry :\n server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象\nexcept socket.error as msg :\n print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])\n sys.exit()\nprint (\"Socket Created!\")\nserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用\ntry : \n server.bind(server_addr) #绑定地址\nexcept socket.error as msg :\n print (\"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])\n sys.exit()\nprint (\"Socket Bind!\")\nserver.listen(5) #监听, 最大监听数为5\nprint (\"Socket listening\")\nwhile True:\n client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数\n print ('Connected by', client_addr)\n while True :\n data = client.recv(BUF_SIZE) #从客户端接收数据\n print (str(data, encoding = \"utf-8\"))\n if data == b'exit':\n break\n client.sendall(data) #发送数据到客户端\nserver.close()\n\n\n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket\n \nBUF_SIZE = 1024 #设置缓冲区的大小\nserver_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址\ntry : \n client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象\nexcept socket.error as msg :\n print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])\n sys.exit()\nclient.connect(server_addr) #要连接的服务器地址\nwhile True:\n data = input(\"Please input some string > \") \n if not data :\n print (\"input can't empty, Please input again..\")\n continue\n client.sendall(bytes(data, encoding = 'utf-8')) #发送数据到服务器\n # client.sendall(str.encode(data)) \n data = client.recv(BUF_SIZE) #从服务器端接收数据\n print (str(data, encoding = \"utf-8\"))\nclient.close()\n```\n\n### UDP\n\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n\nimport socket #socket模块\nBUFF_SIZE = 1024\nserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\naddress = ('127.0.0.1', 12346)\nserver.bind(address)\n\nwhile True:\n print (\"WAIT\")\n data, client_address = server.recvfrom(BUFF_SIZE)\n print (str(data, encoding='utf-8'), 'from' ,client_address)\n server.sendto(data, client_address)\n \n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n\nimport socket #socket模块\nimport sys\nBUFF_SIZE = 1024\nclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\naddress = ('127.0.0.1', 12346)\n\nwhile True:\n data = input(\"Please input some \") \n if data == 'exit':\n break\n client.sendto(bytes(data, encoding='utf-8'), address)\n data = client.recv(BUFF_SIZE)\n print (str(data, encoding='utf-8')) \n```\n\n\n## 别人家的代码【滑稽】:\n\n### TCP\n\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket #socket模块\n \nBUF_SIZE = 1024 #设置缓冲区大小\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\ntry :\n server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象\nexcept socket.error, msg :\n print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]\n sys.exit()\nprint \"Socket Created!\"\nserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用\ntry : \n server.bind(server_addr) #绑定地址\nexcept socket.error, msg :\n print \"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]\n sys.exit()\nprint \"Socket Bind!\"\nserver.listen(5) #监听, 最大监听数为5\nprint \"Socket listening\"\nwhile True:\n client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数\n print 'Connected by', client_addr\n while True :\n data = client.recv(BUF_SIZE) #从客户端接收数据\n print data\n client.sendall(data) #发送数据到客户端\nserver.close()\n\n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport sys\nimport socket\n \n \nBUF_SIZE = 1024 #设置缓冲区的大小\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\ntry : \n client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象\nexcept socket.error, msg :\n print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]\n sys.exit()\nclient.connect(server_addr) #要连接的服务器地址\nwhile True:\n data = raw_input(\"Please input some string > \") \n if not data :\n print \"input can't empty, Please input again..\"\n continue\n client.sendall(data) #发送数据到服务器\n data = client.recv(BUF_SIZE) #从服务器端接收数据\n print data\nclient.close()\n```\n\n### UDP\n```python\n#服务器端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport socket\n \nBUF_SIZE = 1024 #设置缓冲区大小\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\nserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象\nserver.bind(server_addr) #套接字绑定IP和端口\nwhile True :\n print \"waitting for data\"\n data, client_addr = server.recvfrom(BUF_SIZE) #从客户端接收数据\n print 'Connected by', client_addr, ' Receive Data : ', data\n server.sendto(data, client_addr) #发送数据给客户端\nserver.close()\n\n#客户端\n#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n \nimport socket\nimport struct\n \nBUF_SIZE = 1024 #设置缓冲区\nserver_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址\nclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象\n \nwhile True :\n data = raw_input('Please Input data > ')\n client.sendto(data, server_addr) #向服务器发送数据\n data, addr = client.recvfrom(BUF_SIZE) #从服务器接收数据\n print \"Data : \", data\nclient.close()\n```\n\n### 其他\n\n```python\ns.getpeername()\n#返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。\n \ns.getsockname()\n#返回套接字自己的地址。通常是一个元组(ipaddr,port)\n \ns.setsockopt(level,optname,value)\n#设置给定套接字选项的值。\n \ns.getsockopt(level,optname[.buflen])\n#返回套接字选项的值。\n \ns.settimeout(timeout)\n#设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())\n \ns.gettimeout()\n#返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。\n \ns.fileno()\n#返回套接字的文件描述符。\n \ns.setblocking(flag)\n#如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。\n \ns.makefile()\n#创建一个与该套接字相关连的文件\n```\n\n> 转载自 链接地址: http://python.jobbole.com/88396/\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20171219_crawler3","published":1,"date":"2020-01-15T05:50:39.647Z","updated":"2021-03-10T13:50:15.304Z","layout":"post","photos":[],"link":"","_id":"ckm3inve7007w24uj3wpaudxg","content":"
\n

Socket 网络编程

\n
\n

Socket(套接字),是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信, 又称进程间通信, IP协议进行的主要是端到端通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。

\n

所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程

\n

端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023, 用户自定义端口号一般大于等于1024

\n

每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的

\n\n
\n

原文见: http://python.jobbole.com/88396/

\n
\n

个人代码:

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket #socket模块

BUF_SIZE = 1024 #设置缓冲区大小
server_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址
try :
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象
except socket.error as msg :
print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])
sys.exit()
print (\"Socket Created!\")
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用
try :
server.bind(server_addr) #绑定地址
except socket.error as msg :
print (\"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])
sys.exit()
print (\"Socket Bind!\")
server.listen(5) #监听, 最大监听数为5
print (\"Socket listening\")
while True:
client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数
print ('Connected by', client_addr)
while True :
data = client.recv(BUF_SIZE) #从客户端接收数据
print (str(data, encoding = \"utf-8\"))
if data == b'exit':
break
client.sendall(data) #发送数据到客户端
server.close()


#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket

BUF_SIZE = 1024 #设置缓冲区的大小
server_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址
try :
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象
except socket.error as msg :
print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])
sys.exit()
client.connect(server_addr) #要连接的服务器地址
while True:
data = input(\"Please input some string > \")
if not data :
print (\"input can't empty, Please input again..\")
continue
client.sendall(bytes(data, encoding = 'utf-8')) #发送数据到服务器
# client.sendall(str.encode(data))
data = client.recv(BUF_SIZE) #从服务器端接收数据
print (str(data, encoding = \"utf-8\"))
client.close()
\n

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket #socket模块
BUFF_SIZE = 1024
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
address = ('127.0.0.1', 12346)
server.bind(address)

while True:
print (\"WAIT\")
data, client_address = server.recvfrom(BUFF_SIZE)
print (str(data, encoding='utf-8'), 'from' ,client_address)
server.sendto(data, client_address)

#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket #socket模块
import sys
BUFF_SIZE = 1024
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
address = ('127.0.0.1', 12346)

while True:
data = input(\"Please input some \")
if data == 'exit':
break
client.sendto(bytes(data, encoding='utf-8'), address)
data = client.recv(BUFF_SIZE)
print (str(data, encoding='utf-8'))
\n

别人家的代码【滑稽】:

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket #socket模块

BUF_SIZE = 1024 #设置缓冲区大小
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
try :
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象
except socket.error, msg :
print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]
sys.exit()
print \"Socket Created!\"
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用
try :
server.bind(server_addr) #绑定地址
except socket.error, msg :
print \"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]
sys.exit()
print \"Socket Bind!\"
server.listen(5) #监听, 最大监听数为5
print \"Socket listening\"
while True:
client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数
print 'Connected by', client_addr
while True :
data = client.recv(BUF_SIZE) #从客户端接收数据
print data
client.sendall(data) #发送数据到客户端
server.close()

#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket


BUF_SIZE = 1024 #设置缓冲区的大小
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
try :
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象
except socket.error, msg :
print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]
sys.exit()
client.connect(server_addr) #要连接的服务器地址
while True:
data = raw_input(\"Please input some string > \")
if not data :
print \"input can't empty, Please input again..\"
continue
client.sendall(data) #发送数据到服务器
data = client.recv(BUF_SIZE) #从服务器端接收数据
print data
client.close()
\n

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

BUF_SIZE = 1024 #设置缓冲区大小
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象
server.bind(server_addr) #套接字绑定IP和端口
while True :
print \"waitting for data\"
data, client_addr = server.recvfrom(BUF_SIZE) #从客户端接收数据
print 'Connected by', client_addr, ' Receive Data : ', data
server.sendto(data, client_addr) #发送数据给客户端
server.close()

#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import struct

BUF_SIZE = 1024 #设置缓冲区
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象

while True :
data = raw_input('Please Input data > ')
client.sendto(data, server_addr) #向服务器发送数据
data, addr = client.recvfrom(BUF_SIZE) #从服务器接收数据
print \"Data : \", data
client.close()
\n

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
s.getpeername()
#返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname()
#返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value)
#设置给定套接字选项的值。

s.getsockopt(level,optname[.buflen])
#返回套接字选项的值。

s.settimeout(timeout)
#设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())

s.gettimeout()
#返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。

s.fileno()
#返回套接字的文件描述符。

s.setblocking(flag)
#如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()
#创建一个与该套接字相关连的文件
\n
\n

转载自 链接地址: http://python.jobbole.com/88396/

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

Socket 网络编程

\n
\n

Socket(套接字),是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信, 又称进程间通信, IP协议进行的主要是端到端通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。

\n

所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程

\n

端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023, 用户自定义端口号一般大于等于1024

\n

每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的

","more":"
\n

原文见: http://python.jobbole.com/88396/

\n
\n

个人代码:

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket #socket模块

BUF_SIZE = 1024 #设置缓冲区大小
server_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址
try :
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象
except socket.error as msg :
print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])
sys.exit()
print (\"Socket Created!\")
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用
try :
server.bind(server_addr) #绑定地址
except socket.error as msg :
print (\"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])
sys.exit()
print (\"Socket Bind!\")
server.listen(5) #监听, 最大监听数为5
print (\"Socket listening\")
while True:
client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数
print ('Connected by', client_addr)
while True :
data = client.recv(BUF_SIZE) #从客户端接收数据
print (str(data, encoding = \"utf-8\"))
if data == b'exit':
break
client.sendall(data) #发送数据到客户端
server.close()


#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket

BUF_SIZE = 1024 #设置缓冲区的大小
server_addr = ('127.0.0.1', 51230) #IP和端口构成表示地址
try :
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象
except socket.error as msg :
print (\"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1])
sys.exit()
client.connect(server_addr) #要连接的服务器地址
while True:
data = input(\"Please input some string > \")
if not data :
print (\"input can't empty, Please input again..\")
continue
client.sendall(bytes(data, encoding = 'utf-8')) #发送数据到服务器
# client.sendall(str.encode(data))
data = client.recv(BUF_SIZE) #从服务器端接收数据
print (str(data, encoding = \"utf-8\"))
client.close()
\n

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket #socket模块
BUFF_SIZE = 1024
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
address = ('127.0.0.1', 12346)
server.bind(address)

while True:
print (\"WAIT\")
data, client_address = server.recvfrom(BUFF_SIZE)
print (str(data, encoding='utf-8'), 'from' ,client_address)
server.sendto(data, client_address)

#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket #socket模块
import sys
BUFF_SIZE = 1024
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
address = ('127.0.0.1', 12346)

while True:
data = input(\"Please input some \")
if data == 'exit':
break
client.sendto(bytes(data, encoding='utf-8'), address)
data = client.recv(BUFF_SIZE)
print (str(data, encoding='utf-8'))
\n

别人家的代码【滑稽】:

TCP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket #socket模块

BUF_SIZE = 1024 #设置缓冲区大小
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
try :
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #生成一个新的socket对象
except socket.error, msg :
print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]
sys.exit()
print \"Socket Created!\"
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置地址复用
try :
server.bind(server_addr) #绑定地址
except socket.error, msg :
print \"Binding Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]
sys.exit()
print \"Socket Bind!\"
server.listen(5) #监听, 最大监听数为5
print \"Socket listening\"
while True:
client, client_addr = server.accept() #接收TCP连接, 并返回新的套接字和地址, 阻塞函数
print 'Connected by', client_addr
while True :
data = client.recv(BUF_SIZE) #从客户端接收数据
print data
client.sendall(data) #发送数据到客户端
server.close()

#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket


BUF_SIZE = 1024 #设置缓冲区的大小
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
try :
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #返回新的socket对象
except socket.error, msg :
print \"Creating Socket Failure. Error Code : \" + str(msg[0]) + \" Message : \" + msg[1]
sys.exit()
client.connect(server_addr) #要连接的服务器地址
while True:
data = raw_input(\"Please input some string > \")
if not data :
print \"input can't empty, Please input again..\"
continue
client.sendall(data) #发送数据到服务器
data = client.recv(BUF_SIZE) #从服务器端接收数据
print data
client.close()
\n

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#服务器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

BUF_SIZE = 1024 #设置缓冲区大小
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象
server.bind(server_addr) #套接字绑定IP和端口
while True :
print \"waitting for data\"
data, client_addr = server.recvfrom(BUF_SIZE) #从客户端接收数据
print 'Connected by', client_addr, ' Receive Data : ', data
server.sendto(data, client_addr) #发送数据给客户端
server.close()

#客户端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import struct

BUF_SIZE = 1024 #设置缓冲区
server_addr = ('127.0.0.1', 8888) #IP和端口构成表示地址
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #生成新的套接字对象

while True :
data = raw_input('Please Input data > ')
client.sendto(data, server_addr) #向服务器发送数据
data, addr = client.recvfrom(BUF_SIZE) #从服务器接收数据
print \"Data : \", data
client.close()
\n

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
s.getpeername()
#返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname()
#返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value)
#设置给定套接字选项的值。

s.getsockopt(level,optname[.buflen])
#返回套接字选项的值。

s.settimeout(timeout)
#设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())

s.gettimeout()
#返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。

s.fileno()
#返回套接字的文件描述符。

s.setblocking(flag)
#如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()
#创建一个与该套接字相关连的文件
\n
\n

转载自 链接地址: http://python.jobbole.com/88396/

\n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"DBCP连接池介绍(转载)","comments":1,"description":null,"date":"2017-02-11T16:00:00.000Z","_content":"\n*文章发表时间: 2014-03-07,现在情况可能不同* http://www.myhack58.com/Article/60/61/2014/42761.htm\n\n\n DBCP连接池介绍\n \n-----------------------------\n目前 DBCP 有两个版本分别是 1.3 和 1.4。\nDBCP 1.3 版本需要运行于 JDK 1.4-1.5 ,支持 JDBC 3。\nDBCP 1.4 版本需要运行于 JDK 1.6 ,支持 JDBC 4。\n1.3和1.4基于同一套源代码,含有所有的bug修复和新特性。因此在选择DBCP版本的时候,要看你用的是什么JDK版本。\nDBCP1.2版本性能一般,比c3p0差挺多。DBCP1.4和1.3,配合(依赖)commons pool 1.6的jar包,各方面功能、性能推进到新的高峰。相对1.2版本提高不少。超越(或相当)了c3p0.建议使用DBCP1.4或1.3 + commons pool 1.6\n \nTomcat7 中保留DBCP连接池,以兼容已有应用。并提供了新的Tomcat JDBC pool作为DBCP的可选替代。新出的Tomcat JDBC pool,据说比DBCP 1.4要好,未接触,也不在本文讨论范围内。\n \nDBCP连接池配置参数讲解\n \n-----------------------------\n\n\n\n一、Apache官方DBCP文档给出的配置示例:\n可参见:http://tomcat.apache.org/tomcat-6.0-doc/jndi-datasource-examples-howto.html\n\n\n \n\n \n \n二、常用参数说明:\n可参见:http://elf8848.iteye.com/blog/337981\n\n \n \n \nDBCP连接池的自我检测\n \n-----------------------------\n默认配置的DBCP连接池,是不对池中的连接做测试的,有时连接已断开了,但DBCP连接池不知道,还以为连接是好的呢。\n应用从池中取出这样的连接访问数据库一定会报错。这也是好多人不喜欢DBCP的原因。\n \n问题例一:\nMySQL8小时问题,Mysql服务器默认连接的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该 connection。\n但是DBCP连接池并不知道连接已经断开了,如果程序正巧使用到这个已经断开的连接,程序就会报错误。\n \n问题例二:\n 以前还使用Sybase数据库,由于某种原因,数据库死了后重启、或断网后恢复。\n 等了约10分钟后,DBCP连接池中的连接还都是不能使用的(断开的),访问数据应用一直报错,最后只能重启Tomcat问题才解决 。\n \n解决方案:\n 方案1、定时对连接做测试,测试失败就关闭连接。\n 方案2、控制连接的空闲时间达到N分钟,就关闭连接,(然后可再新建连接)。\n 以上两个方案使用任意一个就可以解决以述两类问题。如果只使用方案2,建议 N <= 5分钟。连接断开后最多5分钟后可恢复。\n 也可混合使用两个方案,建议 N = 30分钟。\n \n 下面就是DBCP连接池,同时使用了以上两个方案的配置配置\n\t\t\n validationQuery = \"SELECT 1\" 验证连接是否可用,使用的SQL语句\n testWhileIdle = \"true\" 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.\n testOnBorrow = \"false\" 借出连接时不要测试,否则很影响性能\n\t\t timeBetweenEvictionRunsMillis = \"30000\" 每30秒运行一次空闲连接回收器\n minEvictableIdleTimeMillis = \"1800000\" 池中的连接空闲30分钟后被回收,默认值就是30分钟。\n numTestsPerEvictionRun=\"3\" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量,默认值就是3.\n \n 解释:\n 配置timeBetweenEvictionRunsMillis = \"30000\"后,每30秒运行一次空闲连接回收器(独立纯种)。并每次检查3个连接,如果连接空闲时间超过30分钟就销毁。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接,维护数量不少于minIdle,过行了新老更替。\n testWhileIdle = \"true\" 表示每30秒,取出3条连接,使用validationQuery = \"SELECT 1\" 中的SQL进行测试 ,测试不成功就销毁连接。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接。\n testOnBorrow = \"false\" 一定要配置,因为它的默认值是true。false表示每次从连接池中取出连接时,不需要执行validationQuery = \"SELECT 1\" 中的SQL进行测试。若配置为true,对性能有非常大的影响,性能会下降7-10倍。所在一定要配置为false.\n 每30秒,取出numTestsPerEvictionRun条连接(本例是3,也是默认值),发出\"SELECT 1\" SQL语句进行测试 ,测试过的连接不算是“被使用”了,还算是空闲的。连接空闲30分钟后会被销毁。\n \n ---\n \nDBCP连接池配置参数注意事项 \n \n\nmaxIdle值与maxActive值应配置的接近。\n因为,当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是我想要的空闲M秒后再销毁起一个缓冲作用。这一点DBCP做的可能与你想像的不一样。\n若maxIdle应与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是我想要的。\n高负载的系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。\n \ntimeBetweenEvictionRunsMillis建议设置值\ninitialSize=\"5\",会在tomcat一启动时,创建5条连接,效果很理想。\n但同时我们还配置了minIdle=\"10\",也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?\n1、等业务压力上来了, DBCP就会创建新的连接。\n2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。\n \n ---\n \n连接销毁的逻辑\n \nDBCP的连接数会在 0 - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:\n \n默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。\n目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。\n随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。\n直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。\n但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。\n这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。\n \n默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30000”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。\n目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。\n随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。\n当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。\n这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新连接已使数量稳定在minIdle,并进行着新老更替。\n \n配置initialSize=“10”时,tomcat一启动就创建10条连接。其它同上。\n \nminIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。\n \n ---\n \nTomcat中配置DBCP连接池\n \nTomcat自带DBCP的包,是$CATALINA_HOME/lib/tomcat-dbcp.jar。\nomcat-dbcp.jar含有commons pool、commons DBCP两个包的内容。但只含有与连接池有关的类。\n数据源配置在context.xml文件中, 要在tomcat的lib目录中放jdbc 驱动包\n数据源配置在server.xml的host中,不需要在tomcat的lib目录中放jdbc 驱动包,只使用工程中的jdbc驱动包\n \n \nJNDI配置:更改tomcat的server.xml或context.xml\n \n 全局的数据源:\n 如果需要配置全局的 Resource,则在server.xml的GlobalNamingResources节点里加入Resource,再在Context节点里加入ResourceLink的配置。\n 全局的resource只是为了重用,方便所有该tomcat下的web工程的数据源管理,但如果你的tomcat不会同时加载多个web工程,也就是说一个tomcat只加载一个web工程时,是没有必要配置全局的resource的。\n \n每个web工程一个数据源:\n在$CATALINA_HOME/conf/context.xml的根节点Context里加入Resource配置。这种配置方法,你在context.xml配置了一个数据源,但Tomcat中有同时运行着5个工程,那了就坏事儿了,这个在Tomcat启动时数据源被创建了5份,每个工程1份数据源。连接数会是你配置的参数的5倍。\n只有在你的Tomcat只加载一个web工程时,才可以直接以context.xml配置数据源。\n\n```xml\n\n```\n \n\n 这里的factory指的是该Resource 配置使用的是哪个数据源配置类,这里使用的是tomcat自带的标准数据源Resource配置类,这个类也可以自己写,实现javax.naming.spi.ObjectFactory 接口即可。某些地方使用的commons-dbcp.jar中的org.apache.commons.dbcp.BasicDataSourceFactory,如果使用这个就需把commons-dbcp.jar及其依赖的jar包,都放在tomcat的lib下,光放在工程的WEB-INF/lib下是不够的。\n \n ResourceLink 的配置有多种:\n \n 1)tomcat安装目录下的conf/context.xml,把全局的resource直接公开给该tomcat下的所有web工程,在Context节点中加入:\n \n不建议在此文件中,不使用,而使用直接配置数据源,原因上面已说明了。 \n \n 2)tomcat安装目录下的conf/server.xml,该方法可以指定把哪些source绑定到哪个web工程下。\n\n \n \n\n也可在此文件中,不使用,而使用直接配置数据源。\n \n 3)安装目录下的conf/localhost/下建立一个xml文件,文件名是.xml。比如工程名为test,则该xml名为test.xml。\n\n \n \n\n也可在此文件中,不使用,而使用直接配置数据源。\n \n 4)tomcat安装目录下的\\webapps\\test\\META-INF\\context.xml的Context节点中增加:\n\n也可在此文件中,不使用,而使用直接配置数据源。\n \n \n本文内容都在tomcat6.0上运行测试过,还下载了commons DBCP的源码,加入了跟踪日志,用于验证本文的理论。\n","source":"_posts/2017_DBCP.md","raw":"---\ntitle: DBCP连接池介绍(转载)\n\ncomments: true \n\ntags: \n - DBCP\n\ncategories: \n - 数据库连接池\n\ndescription: \n\ndate: 2017-02-12\n \n---\n\n*文章发表时间: 2014-03-07,现在情况可能不同* http://www.myhack58.com/Article/60/61/2014/42761.htm\n\n\n DBCP连接池介绍\n \n-----------------------------\n目前 DBCP 有两个版本分别是 1.3 和 1.4。\nDBCP 1.3 版本需要运行于 JDK 1.4-1.5 ,支持 JDBC 3。\nDBCP 1.4 版本需要运行于 JDK 1.6 ,支持 JDBC 4。\n1.3和1.4基于同一套源代码,含有所有的bug修复和新特性。因此在选择DBCP版本的时候,要看你用的是什么JDK版本。\nDBCP1.2版本性能一般,比c3p0差挺多。DBCP1.4和1.3,配合(依赖)commons pool 1.6的jar包,各方面功能、性能推进到新的高峰。相对1.2版本提高不少。超越(或相当)了c3p0.建议使用DBCP1.4或1.3 + commons pool 1.6\n \nTomcat7 中保留DBCP连接池,以兼容已有应用。并提供了新的Tomcat JDBC pool作为DBCP的可选替代。新出的Tomcat JDBC pool,据说比DBCP 1.4要好,未接触,也不在本文讨论范围内。\n \nDBCP连接池配置参数讲解\n \n-----------------------------\n\n\n\n一、Apache官方DBCP文档给出的配置示例:\n可参见:http://tomcat.apache.org/tomcat-6.0-doc/jndi-datasource-examples-howto.html\n\n\n \n\n \n \n二、常用参数说明:\n可参见:http://elf8848.iteye.com/blog/337981\n\n \n \n \nDBCP连接池的自我检测\n \n-----------------------------\n默认配置的DBCP连接池,是不对池中的连接做测试的,有时连接已断开了,但DBCP连接池不知道,还以为连接是好的呢。\n应用从池中取出这样的连接访问数据库一定会报错。这也是好多人不喜欢DBCP的原因。\n \n问题例一:\nMySQL8小时问题,Mysql服务器默认连接的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该 connection。\n但是DBCP连接池并不知道连接已经断开了,如果程序正巧使用到这个已经断开的连接,程序就会报错误。\n \n问题例二:\n 以前还使用Sybase数据库,由于某种原因,数据库死了后重启、或断网后恢复。\n 等了约10分钟后,DBCP连接池中的连接还都是不能使用的(断开的),访问数据应用一直报错,最后只能重启Tomcat问题才解决 。\n \n解决方案:\n 方案1、定时对连接做测试,测试失败就关闭连接。\n 方案2、控制连接的空闲时间达到N分钟,就关闭连接,(然后可再新建连接)。\n 以上两个方案使用任意一个就可以解决以述两类问题。如果只使用方案2,建议 N <= 5分钟。连接断开后最多5分钟后可恢复。\n 也可混合使用两个方案,建议 N = 30分钟。\n \n 下面就是DBCP连接池,同时使用了以上两个方案的配置配置\n\t\t\n validationQuery = \"SELECT 1\" 验证连接是否可用,使用的SQL语句\n testWhileIdle = \"true\" 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.\n testOnBorrow = \"false\" 借出连接时不要测试,否则很影响性能\n\t\t timeBetweenEvictionRunsMillis = \"30000\" 每30秒运行一次空闲连接回收器\n minEvictableIdleTimeMillis = \"1800000\" 池中的连接空闲30分钟后被回收,默认值就是30分钟。\n numTestsPerEvictionRun=\"3\" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量,默认值就是3.\n \n 解释:\n 配置timeBetweenEvictionRunsMillis = \"30000\"后,每30秒运行一次空闲连接回收器(独立纯种)。并每次检查3个连接,如果连接空闲时间超过30分钟就销毁。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接,维护数量不少于minIdle,过行了新老更替。\n testWhileIdle = \"true\" 表示每30秒,取出3条连接,使用validationQuery = \"SELECT 1\" 中的SQL进行测试 ,测试不成功就销毁连接。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接。\n testOnBorrow = \"false\" 一定要配置,因为它的默认值是true。false表示每次从连接池中取出连接时,不需要执行validationQuery = \"SELECT 1\" 中的SQL进行测试。若配置为true,对性能有非常大的影响,性能会下降7-10倍。所在一定要配置为false.\n 每30秒,取出numTestsPerEvictionRun条连接(本例是3,也是默认值),发出\"SELECT 1\" SQL语句进行测试 ,测试过的连接不算是“被使用”了,还算是空闲的。连接空闲30分钟后会被销毁。\n \n ---\n \nDBCP连接池配置参数注意事项 \n \n\nmaxIdle值与maxActive值应配置的接近。\n因为,当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是我想要的空闲M秒后再销毁起一个缓冲作用。这一点DBCP做的可能与你想像的不一样。\n若maxIdle应与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是我想要的。\n高负载的系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。\n \ntimeBetweenEvictionRunsMillis建议设置值\ninitialSize=\"5\",会在tomcat一启动时,创建5条连接,效果很理想。\n但同时我们还配置了minIdle=\"10\",也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?\n1、等业务压力上来了, DBCP就会创建新的连接。\n2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。\n \n ---\n \n连接销毁的逻辑\n \nDBCP的连接数会在 0 - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:\n \n默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。\n目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。\n随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。\n直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。\n但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。\n这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。\n \n默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30000”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。\n目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。\n随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。\n当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。\n这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新连接已使数量稳定在minIdle,并进行着新老更替。\n \n配置initialSize=“10”时,tomcat一启动就创建10条连接。其它同上。\n \nminIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。\n \n ---\n \nTomcat中配置DBCP连接池\n \nTomcat自带DBCP的包,是$CATALINA_HOME/lib/tomcat-dbcp.jar。\nomcat-dbcp.jar含有commons pool、commons DBCP两个包的内容。但只含有与连接池有关的类。\n数据源配置在context.xml文件中, 要在tomcat的lib目录中放jdbc 驱动包\n数据源配置在server.xml的host中,不需要在tomcat的lib目录中放jdbc 驱动包,只使用工程中的jdbc驱动包\n \n \nJNDI配置:更改tomcat的server.xml或context.xml\n \n 全局的数据源:\n 如果需要配置全局的 Resource,则在server.xml的GlobalNamingResources节点里加入Resource,再在Context节点里加入ResourceLink的配置。\n 全局的resource只是为了重用,方便所有该tomcat下的web工程的数据源管理,但如果你的tomcat不会同时加载多个web工程,也就是说一个tomcat只加载一个web工程时,是没有必要配置全局的resource的。\n \n每个web工程一个数据源:\n在$CATALINA_HOME/conf/context.xml的根节点Context里加入Resource配置。这种配置方法,你在context.xml配置了一个数据源,但Tomcat中有同时运行着5个工程,那了就坏事儿了,这个在Tomcat启动时数据源被创建了5份,每个工程1份数据源。连接数会是你配置的参数的5倍。\n只有在你的Tomcat只加载一个web工程时,才可以直接以context.xml配置数据源。\n\n```xml\n\n```\n \n\n 这里的factory指的是该Resource 配置使用的是哪个数据源配置类,这里使用的是tomcat自带的标准数据源Resource配置类,这个类也可以自己写,实现javax.naming.spi.ObjectFactory 接口即可。某些地方使用的commons-dbcp.jar中的org.apache.commons.dbcp.BasicDataSourceFactory,如果使用这个就需把commons-dbcp.jar及其依赖的jar包,都放在tomcat的lib下,光放在工程的WEB-INF/lib下是不够的。\n \n ResourceLink 的配置有多种:\n \n 1)tomcat安装目录下的conf/context.xml,把全局的resource直接公开给该tomcat下的所有web工程,在Context节点中加入:\n \n不建议在此文件中,不使用,而使用直接配置数据源,原因上面已说明了。 \n \n 2)tomcat安装目录下的conf/server.xml,该方法可以指定把哪些source绑定到哪个web工程下。\n\n \n \n\n也可在此文件中,不使用,而使用直接配置数据源。\n \n 3)安装目录下的conf/localhost/下建立一个xml文件,文件名是.xml。比如工程名为test,则该xml名为test.xml。\n\n \n \n\n也可在此文件中,不使用,而使用直接配置数据源。\n \n 4)tomcat安装目录下的\\webapps\\test\\META-INF\\context.xml的Context节点中增加:\n\n也可在此文件中,不使用,而使用直接配置数据源。\n \n \n本文内容都在tomcat6.0上运行测试过,还下载了commons DBCP的源码,加入了跟踪日志,用于验证本文的理论。\n","slug":"2017_DBCP","published":1,"updated":"2018-01-13T02:29:22.217Z","layout":"post","photos":[],"link":"","_id":"ckm3inve8007z24ujh019o0ve","content":"

文章发表时间: 2014-03-07,现在情况可能不同 http://www.myhack58.com/Article/60/61/2014/42761.htm

\n

DBCP连接池介绍

\n
\n

目前 DBCP 有两个版本分别是 1.3 和 1.4。
DBCP 1.3 版本需要运行于 JDK 1.4-1.5 ,支持 JDBC 3。
DBCP 1.4 版本需要运行于 JDK 1.6 ,支持 JDBC 4。
1.3和1.4基于同一套源代码,含有所有的bug修复和新特性。因此在选择DBCP版本的时候,要看你用的是什么JDK版本。
DBCP1.2版本性能一般,比c3p0差挺多。DBCP1.4和1.3,配合(依赖)commons pool 1.6的jar包,各方面功能、性能推进到新的高峰。相对1.2版本提高不少。超越(或相当)了c3p0.建议使用DBCP1.4或1.3 + commons pool 1.6

\n

Tomcat7 中保留DBCP连接池,以兼容已有应用。并提供了新的Tomcat JDBC pool作为DBCP的可选替代。新出的Tomcat JDBC pool,据说比DBCP 1.4要好,未接触,也不在本文讨论范围内。

\n

DBCP连接池配置参数讲解

\n
\n\n

一、Apache官方DBCP文档给出的配置示例:
可参见:http://tomcat.apache.org/tomcat-6.0-doc/jndi-datasource-examples-howto.html

\n

\n\n\n

二、常用参数说明:
可参见:http://elf8848.iteye.com/blog/337981

\n
<Resource\n    name="jdbc/TestDB"  JNDI数据源的name\n    type="javax.sql.DataSource"\n\n    driverClassName="com.mysql.jdbc.Driver" JDBC驱动类\n    url=""\n    username="" 访问数据库用户名\n    password="" 访问数据库的密码\n\n    maxActive="80" 最大活动连接 //我使用版本是2.1,最大连接名称变为maxTotal\n    initialSize="10"  初始化连接\n    maxIdle="60"   最大空闲连接\n    minIdle="10"   最小空闲连接\n    maxWait="3000" 从池中取连接的最大等待时间,单位ms.\n\n    validationQuery = "SELECT 1"  验证使用的SQL语句\n    testWhileIdle = "true"      指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.\n    testOnBorrow = "false"   借出连接时不要测试,否则很影响性能\n    timeBetweenEvictionRunsMillis = "30000"  每30秒运行一次空闲连接回收器\n    minEvictableIdleTimeMillis = "1800000"  池中的连接空闲30分钟后被回收\n    numTestsPerEvictionRun="3" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量\n\n    removeAbandoned="true"  连接泄漏回收参数,当可用连接数少于3个时才执行\n    removeAbandonedTimeout="180"  连接泄漏回收参数,180秒,泄露的连接可以被删除的超时值\n/>\n

DBCP连接池的自我检测

\n
\n

默认配置的DBCP连接池,是不对池中的连接做测试的,有时连接已断开了,但DBCP连接池不知道,还以为连接是好的呢。
应用从池中取出这样的连接访问数据库一定会报错。这也是好多人不喜欢DBCP的原因。

\n

问题例一:
MySQL8小时问题,Mysql服务器默认连接的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该 connection。
但是DBCP连接池并不知道连接已经断开了,如果程序正巧使用到这个已经断开的连接,程序就会报错误。

\n

问题例二:
以前还使用Sybase数据库,由于某种原因,数据库死了后重启、或断网后恢复。
等了约10分钟后,DBCP连接池中的连接还都是不能使用的(断开的),访问数据应用一直报错,最后只能重启Tomcat问题才解决 。

\n

解决方案:
方案1、定时对连接做测试,测试失败就关闭连接。
方案2、控制连接的空闲时间达到N分钟,就关闭连接,(然后可再新建连接)。
以上两个方案使用任意一个就可以解决以述两类问题。如果只使用方案2,建议 N <= 5分钟。连接断开后最多5分钟后可恢复。
也可混合使用两个方案,建议 N = 30分钟。

\n
下面就是DBCP连接池,同时使用了以上两个方案的配置配置\n\nvalidationQuery = "SELECT 1"  验证连接是否可用,使用的SQL语句\ntestWhileIdle = "true"      指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.\ntestOnBorrow = "false"   借出连接时不要测试,否则很影响性能\n      timeBetweenEvictionRunsMillis = "30000"  每30秒运行一次空闲连接回收器\nminEvictableIdleTimeMillis = "1800000"  池中的连接空闲30分钟后被回收,默认值就是30分钟。\nnumTestsPerEvictionRun="3" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量,默认值就是3.\n\n解释:\n配置timeBetweenEvictionRunsMillis = "30000"后,每30秒运行一次空闲连接回收器(独立纯种)。并每次检查3个连接,如果连接空闲时间超过30分钟就销毁。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接,维护数量不少于minIdle,过行了新老更替。\ntestWhileIdle = "true" 表示每30秒,取出3条连接,使用validationQuery = "SELECT 1" 中的SQL进行测试 ,测试不成功就销毁连接。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接。\ntestOnBorrow = "false" 一定要配置,因为它的默认值是true。false表示每次从连接池中取出连接时,不需要执行validationQuery = "SELECT 1" 中的SQL进行测试。若配置为true,对性能有非常大的影响,性能会下降7-10倍。所在一定要配置为false.\n每30秒,取出numTestsPerEvictionRun条连接(本例是3,也是默认值),发出"SELECT 1" SQL语句进行测试 ,测试过的连接不算是“被使用”了,还算是空闲的。连接空闲30分钟后会被销毁。\n

\n

DBCP连接池配置参数注意事项

\n

maxIdle值与maxActive值应配置的接近。
因为,当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是我想要的空闲M秒后再销毁起一个缓冲作用。这一点DBCP做的可能与你想像的不一样。
若maxIdle应与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是我想要的。
高负载的系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。

\n

timeBetweenEvictionRunsMillis建议设置值
initialSize=”5”,会在tomcat一启动时,创建5条连接,效果很理想。
但同时我们还配置了minIdle=”10”,也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?
1、等业务压力上来了, DBCP就会创建新的连接。
2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。

\n
\n

连接销毁的逻辑

\n

DBCP的连接数会在 0 - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:

\n

默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。
随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。
直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。
但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。

\n

默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30000”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。
随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。
当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新连接已使数量稳定在minIdle,并进行着新老更替。

\n

配置initialSize=“10”时,tomcat一启动就创建10条连接。其它同上。

\n

minIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。

\n
\n

Tomcat中配置DBCP连接池

\n

Tomcat自带DBCP的包,是$CATALINA_HOME/lib/tomcat-dbcp.jar。
omcat-dbcp.jar含有commons pool、commons DBCP两个包的内容。但只含有与连接池有关的类。
数据源配置在context.xml文件中, 要在tomcat的lib目录中放jdbc 驱动包
数据源配置在server.xml的host中,不需要在tomcat的lib目录中放jdbc 驱动包,只使用工程中的jdbc驱动包

\n

JNDI配置:更改tomcat的server.xml或context.xml

\n
全局的数据源:\n如果需要配置全局的 Resource,则在server.xml的GlobalNamingResources节点里加入Resource,再在Context节点里加入ResourceLink的配置。\n全局的resource只是为了重用,方便所有该tomcat下的web工程的数据源管理,但如果你的tomcat不会同时加载多个web工程,也就是说一个tomcat只加载一个web工程时,是没有必要配置全局的resource的。\n

每个web工程一个数据源:
在$CATALINA_HOME/conf/context.xml的根节点Context里加入Resource配置。这种配置方法,你在context.xml配置了一个数据源,但Tomcat中有同时运行着5个工程,那了就坏事儿了,这个在Tomcat启动时数据源被创建了5份,每个工程1份数据源。连接数会是你配置的参数的5倍。
只有在你的Tomcat只加载一个web工程时,才可以直接以context.xml配置数据源。

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
<Resource name=\"jdbc/testDB\"       //指定的jndi名称,会用于spring数据源bean的配置和ResourceLink的配置
type=\"javax.sql.DataSource\" //数据源床型,使用标准的javax.sql.DataSource
driverClassName=\"com.mysql.jdbc.Driver\" //JDBC驱动器
url=\"jdbc:mysql://localhost:3306/test\" //数据库URL地址
username=\"test\" //数据库用户名
password=\"test\" //数据库密码
maxIdle=\"40\" //最大的空闲连接数
maxWait=\"4000\" //当池的数据库连接已经被占用的时候,最大等待时间
maxActive=\"40\" //连接池当中最大的数据库连接
removeAbandoned=\"true\"
removeAbandonedTimeout=\"180\"
logAbandoned=\"true\" //被丢弃的数据库连接是否做记录,以便跟踪
factory=\"org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory\" />
\n
 这里的factory指的是该Resource 配置使用的是哪个数据源配置类,这里使用的是tomcat自带的标准数据源Resource配置类,这个类也可以自己写,实现javax.naming.spi.ObjectFactory 接口即可。某些地方使用的commons-dbcp.jar中的org.apache.commons.dbcp.BasicDataSourceFactory,如果使用这个就需把commons-dbcp.jar及其依赖的jar包,都放在tomcat的lib下,光放在工程的WEB-INF/lib下是不够的。\n\nResourceLink 的配置有多种:\n\n1)tomcat安装目录下的conf/context.xml,把全局的resource直接公开给该tomcat下的所有web工程,在Context节点中加入:\n


不建议在此文件中,不使用,而使用直接配置数据源,原因上面已说明了。

\n
2)tomcat安装目录下的conf/server.xml,该方法可以指定把哪些source绑定到哪个web工程下。\n
\n




也可在此文件中,不使用,而使用直接配置数据源。

\n
3)安装目录下的conf/localhost/下建立一个xml文件,文件名是<yourAppName>.xml。比如工程名为test,则该xml名为test.xml。\n

<?xml version=”1.0” encoding=”UTF-8”?>

\n




也可在此文件中,不使用,而使用直接配置数据源。

\n
4)tomcat安装目录下的\\webapps\\test\\META-INF\\context.xml的Context节点中增加:\n


也可在此文件中,不使用,而使用直接配置数据源。

\n

本文内容都在tomcat6.0上运行测试过,还下载了commons DBCP的源码,加入了跟踪日志,用于验证本文的理论。

\n","site":{"data":{}},"excerpt":"

文章发表时间: 2014-03-07,现在情况可能不同 http://www.myhack58.com/Article/60/61/2014/42761.htm

\n

DBCP连接池介绍

\n
\n

目前 DBCP 有两个版本分别是 1.3 和 1.4。
DBCP 1.3 版本需要运行于 JDK 1.4-1.5 ,支持 JDBC 3。
DBCP 1.4 版本需要运行于 JDK 1.6 ,支持 JDBC 4。
1.3和1.4基于同一套源代码,含有所有的bug修复和新特性。因此在选择DBCP版本的时候,要看你用的是什么JDK版本。
DBCP1.2版本性能一般,比c3p0差挺多。DBCP1.4和1.3,配合(依赖)commons pool 1.6的jar包,各方面功能、性能推进到新的高峰。相对1.2版本提高不少。超越(或相当)了c3p0.建议使用DBCP1.4或1.3 + commons pool 1.6

\n

Tomcat7 中保留DBCP连接池,以兼容已有应用。并提供了新的Tomcat JDBC pool作为DBCP的可选替代。新出的Tomcat JDBC pool,据说比DBCP 1.4要好,未接触,也不在本文讨论范围内。

\n

DBCP连接池配置参数讲解

\n
","more":"

一、Apache官方DBCP文档给出的配置示例:
可参见:http://tomcat.apache.org/tomcat-6.0-doc/jndi-datasource-examples-howto.html

\n

\n\n\n

二、常用参数说明:
可参见:http://elf8848.iteye.com/blog/337981

\n
<Resource\n    name="jdbc/TestDB"  JNDI数据源的name\n    type="javax.sql.DataSource"\n\n    driverClassName="com.mysql.jdbc.Driver" JDBC驱动类\n    url=""\n    username="" 访问数据库用户名\n    password="" 访问数据库的密码\n\n    maxActive="80" 最大活动连接 //我使用版本是2.1,最大连接名称变为maxTotal\n    initialSize="10"  初始化连接\n    maxIdle="60"   最大空闲连接\n    minIdle="10"   最小空闲连接\n    maxWait="3000" 从池中取连接的最大等待时间,单位ms.\n\n    validationQuery = "SELECT 1"  验证使用的SQL语句\n    testWhileIdle = "true"      指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.\n    testOnBorrow = "false"   借出连接时不要测试,否则很影响性能\n    timeBetweenEvictionRunsMillis = "30000"  每30秒运行一次空闲连接回收器\n    minEvictableIdleTimeMillis = "1800000"  池中的连接空闲30分钟后被回收\n    numTestsPerEvictionRun="3" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量\n\n    removeAbandoned="true"  连接泄漏回收参数,当可用连接数少于3个时才执行\n    removeAbandonedTimeout="180"  连接泄漏回收参数,180秒,泄露的连接可以被删除的超时值\n/>\n

DBCP连接池的自我检测

\n
\n

默认配置的DBCP连接池,是不对池中的连接做测试的,有时连接已断开了,但DBCP连接池不知道,还以为连接是好的呢。
应用从池中取出这样的连接访问数据库一定会报错。这也是好多人不喜欢DBCP的原因。

\n

问题例一:
MySQL8小时问题,Mysql服务器默认连接的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该 connection。
但是DBCP连接池并不知道连接已经断开了,如果程序正巧使用到这个已经断开的连接,程序就会报错误。

\n

问题例二:
以前还使用Sybase数据库,由于某种原因,数据库死了后重启、或断网后恢复。
等了约10分钟后,DBCP连接池中的连接还都是不能使用的(断开的),访问数据应用一直报错,最后只能重启Tomcat问题才解决 。

\n

解决方案:
方案1、定时对连接做测试,测试失败就关闭连接。
方案2、控制连接的空闲时间达到N分钟,就关闭连接,(然后可再新建连接)。
以上两个方案使用任意一个就可以解决以述两类问题。如果只使用方案2,建议 N <= 5分钟。连接断开后最多5分钟后可恢复。
也可混合使用两个方案,建议 N = 30分钟。

\n
下面就是DBCP连接池,同时使用了以上两个方案的配置配置\n\nvalidationQuery = "SELECT 1"  验证连接是否可用,使用的SQL语句\ntestWhileIdle = "true"      指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.\ntestOnBorrow = "false"   借出连接时不要测试,否则很影响性能\n      timeBetweenEvictionRunsMillis = "30000"  每30秒运行一次空闲连接回收器\nminEvictableIdleTimeMillis = "1800000"  池中的连接空闲30分钟后被回收,默认值就是30分钟。\nnumTestsPerEvictionRun="3" 在每次空闲连接回收器线程(如果有)运行时检查的连接数量,默认值就是3.\n\n解释:\n配置timeBetweenEvictionRunsMillis = "30000"后,每30秒运行一次空闲连接回收器(独立纯种)。并每次检查3个连接,如果连接空闲时间超过30分钟就销毁。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接,维护数量不少于minIdle,过行了新老更替。\ntestWhileIdle = "true" 表示每30秒,取出3条连接,使用validationQuery = "SELECT 1" 中的SQL进行测试 ,测试不成功就销毁连接。销毁连接后,连接数量就少了,如果小于minIdle数量,就新建连接。\ntestOnBorrow = "false" 一定要配置,因为它的默认值是true。false表示每次从连接池中取出连接时,不需要执行validationQuery = "SELECT 1" 中的SQL进行测试。若配置为true,对性能有非常大的影响,性能会下降7-10倍。所在一定要配置为false.\n每30秒,取出numTestsPerEvictionRun条连接(本例是3,也是默认值),发出"SELECT 1" SQL语句进行测试 ,测试过的连接不算是“被使用”了,还算是空闲的。连接空闲30分钟后会被销毁。\n

\n

DBCP连接池配置参数注意事项

\n

maxIdle值与maxActive值应配置的接近。
因为,当连接数超过maxIdle值后,刚刚使用完的连接(刚刚空闲下来)会立即被销毁。而不是我想要的空闲M秒后再销毁起一个缓冲作用。这一点DBCP做的可能与你想像的不一样。
若maxIdle应与maxActive相差较大,在高负载的系统中会导致频繁的创建、销毁连接,连接数在maxIdle与maxActive间快速频繁波动,这不是我想要的。
高负载的系统的maxIdle值可以设置为与maxActive相同或设置为-1(-1表示不限制),让连接数量在minIdle与maxIdle间缓冲慢速波动。

\n

timeBetweenEvictionRunsMillis建议设置值
initialSize=”5”,会在tomcat一启动时,创建5条连接,效果很理想。
但同时我们还配置了minIdle=”10”,也就是说,最少要保持10条连接,那现在只有5条连接,哪什么时候再创建少的5条连接呢?
1、等业务压力上来了, DBCP就会创建新的连接。
2、配置timeBetweenEvictionRunsMillis=“时间”,DBCP会启用独立的工作线程定时检查,补上少的5条连接。销毁多余的连接也是同理。

\n
\n

连接销毁的逻辑

\n

DBCP的连接数会在 0 - minIdle - maxIdle - maxActive 之间变化。变化的逻辑描述如下:

\n

默认未配置initialSize(默认值是0)和timeBetweenEvictionRunsMillis参数时,刚启动tomcat时,连接数是0。当应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,但DBCP也不自动创建新连接已使数量达到minIdle数量(没有一个独立的工作线程来检查和创建)。
随着应用并发访问数据库的增多,连接数也增多,但都与minIdle值无关,很快minIdle被超越,minIdle值一点用都没有。
直到连接的数量达到maxIdle值,这时的连接都是只增不减的。 再继续发展,连接数再增多并超过maxIdle时,使用完的连接(刚刚空闲下来的)会立即关闭,总体连接的数量稳定在maxIdle但不会超过maxIdle。
但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,连接数也不会减少(没有一个独立的线程来检查和销毁),将保持在maxIdle的数量。

\n

默认未配置initialSize(默认值是0),但配置了timeBetweenEvictionRunsMillis=“30000”(30秒)参数时,刚启动tomcat时,连接数是0。马上应用有一个并发访问数据库时DBCP创建一个连接。
目前连接数量还未达到minIdle,每30秒DBCP的工作线程检查连接数是否少于minIdle数量,若少于就创建新连接直到达到minIdle数量。
随着应用并发访问数据库的增多,连接数也增多,直到达到maxIdle值。这期间每30秒DBCP的工作线程检查连接是否空闲了30分钟,若是就销毁。但此时是业务的高峰期,是不会有长达30分钟的空闲连接的,工作线程查了也是白查,但它在工作。到这里连接数量一直是呈现增长的趋势。
当连接数再增多超过maxIdle时,使用完的连接(刚刚空闲下来)会立即关闭,总体连接的数量稳定在maxIdle。停止了增长的趋势。但活动连接(在使用中的连接)可能数量上瞬间超过maxIdle,但永远不会超过maxActive。
这时如果应用业务压力小了,访问数据库的并发少了,每30秒DBCP的工作线程检查连接(默认每次查3条)是否空闲达到30分钟(这是默认值),若连接空闲达到30分钟,就销毁连接。这时连接数减少了,呈下降趋势,将从maxIdle走向minIdle。当小于minIdle值时,则DBCP创建新连接已使数量稳定在minIdle,并进行着新老更替。

\n

配置initialSize=“10”时,tomcat一启动就创建10条连接。其它同上。

\n

minIdle要与timeBetweenEvictionRunsMillis配合使用才有用,单独使用minIdle不会起作用。

\n
\n

Tomcat中配置DBCP连接池

\n

Tomcat自带DBCP的包,是$CATALINA_HOME/lib/tomcat-dbcp.jar。
omcat-dbcp.jar含有commons pool、commons DBCP两个包的内容。但只含有与连接池有关的类。
数据源配置在context.xml文件中, 要在tomcat的lib目录中放jdbc 驱动包
数据源配置在server.xml的host中,不需要在tomcat的lib目录中放jdbc 驱动包,只使用工程中的jdbc驱动包

\n

JNDI配置:更改tomcat的server.xml或context.xml

\n
全局的数据源:\n如果需要配置全局的 Resource,则在server.xml的GlobalNamingResources节点里加入Resource,再在Context节点里加入ResourceLink的配置。\n全局的resource只是为了重用,方便所有该tomcat下的web工程的数据源管理,但如果你的tomcat不会同时加载多个web工程,也就是说一个tomcat只加载一个web工程时,是没有必要配置全局的resource的。\n

每个web工程一个数据源:
在$CATALINA_HOME/conf/context.xml的根节点Context里加入Resource配置。这种配置方法,你在context.xml配置了一个数据源,但Tomcat中有同时运行着5个工程,那了就坏事儿了,这个在Tomcat启动时数据源被创建了5份,每个工程1份数据源。连接数会是你配置的参数的5倍。
只有在你的Tomcat只加载一个web工程时,才可以直接以context.xml配置数据源。

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
<Resource name=\"jdbc/testDB\"       //指定的jndi名称,会用于spring数据源bean的配置和ResourceLink的配置
type=\"javax.sql.DataSource\" //数据源床型,使用标准的javax.sql.DataSource
driverClassName=\"com.mysql.jdbc.Driver\" //JDBC驱动器
url=\"jdbc:mysql://localhost:3306/test\" //数据库URL地址
username=\"test\" //数据库用户名
password=\"test\" //数据库密码
maxIdle=\"40\" //最大的空闲连接数
maxWait=\"4000\" //当池的数据库连接已经被占用的时候,最大等待时间
maxActive=\"40\" //连接池当中最大的数据库连接
removeAbandoned=\"true\"
removeAbandonedTimeout=\"180\"
logAbandoned=\"true\" //被丢弃的数据库连接是否做记录,以便跟踪
factory=\"org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory\" />
\n
 这里的factory指的是该Resource 配置使用的是哪个数据源配置类,这里使用的是tomcat自带的标准数据源Resource配置类,这个类也可以自己写,实现javax.naming.spi.ObjectFactory 接口即可。某些地方使用的commons-dbcp.jar中的org.apache.commons.dbcp.BasicDataSourceFactory,如果使用这个就需把commons-dbcp.jar及其依赖的jar包,都放在tomcat的lib下,光放在工程的WEB-INF/lib下是不够的。\n\nResourceLink 的配置有多种:\n\n1)tomcat安装目录下的conf/context.xml,把全局的resource直接公开给该tomcat下的所有web工程,在Context节点中加入:\n


不建议在此文件中,不使用,而使用直接配置数据源,原因上面已说明了。

\n
2)tomcat安装目录下的conf/server.xml,该方法可以指定把哪些source绑定到哪个web工程下。\n
\n




也可在此文件中,不使用,而使用直接配置数据源。

\n
3)安装目录下的conf/localhost/下建立一个xml文件,文件名是<yourAppName>.xml。比如工程名为test,则该xml名为test.xml。\n

<?xml version=”1.0” encoding=”UTF-8”?>

\n




也可在此文件中,不使用,而使用直接配置数据源。

\n
4)tomcat安装目录下的\\webapps\\test\\META-INF\\context.xml的Context节点中增加:\n


也可在此文件中,不使用,而使用直接配置数据源。

\n

本文内容都在tomcat6.0上运行测试过,还下载了commons DBCP的源码,加入了跟踪日志,用于验证本文的理论。

"},{"title":"Hibernate配置以及实体映射配置","comments":1,"description":"Hibernate配置以及配置介绍 + 实体映射文件配置以及配置介绍 + 简单的创建数据库测试","date":"2017-02-26T16:00:00.000Z","_content":"\n# **Hibernate**是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,,它将POJO与数据库表建立映射关系\n# **Hibernate**是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库\n---\n## 下面是测试用实体1---Student.java\n\n\n\n \n package com.em.entity;\n \n /**\n * Created by zhangjin on 2017/2/27.\n */\n public class Student {\n private Integer id;\n private String name;\n private Integer age;\n private Double score;\n \n private Course course;\n \n public Student() {\n }\n \n public Student(String name, Integer age, Double score) {\n this.name = name;\n this.age = age;\n this.score = score;\n }\n \n public Student(String name, Integer age, Double score, Course course) {\n this.name = name;\n this.age = age;\n this.score = score;\n this.course = course;\n }\n \n public Integer getId() {\n return id;\n }\n \n public void setId(Integer id) {\n this.id = id;\n }\n \n public String getName() {\n return name;\n }\n \n public void setName(String name) {\n this.name = name;\n }\n \n public Integer getAge() {\n return age;\n }\n \n public void setAge(Integer age) {\n this.age = age;\n }\n \n public Double getScore() {\n return score;\n }\n \n public void setScore(Double score) {\n this.score = score;\n }\n \n public Course getCourse() {\n return course;\n }\n \n public void setCourse(Course course) {\n this.course = course;\n }\n \n @Override\n public String toString() {\n return \"Student{\" +\n \"id=\" + id +\n \", name='\" + name + '\\'' +\n \", age=\" + age +\n \", course=\" + course +\n '}';\n }\n }\n\n\n## 其所对应的Hibernate映射文件为 Student.hbm.xml\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n :score]]>\n \n \n\n\n---\n## 下面是测试用实体2---Course.java\n \n \n package com.em.entity;\n \n import java.sql.Timestamp;\n import java.util.HashSet;\n import java.util.Set;\n \n /**\n * Created by zhangjin on 2017/2/27.\n */\n \n public class Course {\n private Integer id;\n private String courseName;\n private Timestamp courseTime;\n \n Set stuSet = new HashSet<>();\n \n public Course() {\n }\n \n public Course(String courseName, Timestamp courseTime) {\n this.courseName = courseName;\n this.courseTime = courseTime;\n }\n \n public Integer getId() {\n return id;\n }\n \n public void setId(Integer id) {\n this.id = id;\n }\n \n public String getCourseName() {\n return courseName;\n }\n \n public void setCourseName(String courseName) {\n this.courseName = courseName;\n }\n \n public Timestamp getCourseTime() {\n return courseTime;\n }\n \n public void setCourseTime(Timestamp courseTime) {\n this.courseTime = courseTime;\n }\n \n public Set getStuSet() {\n return stuSet;\n }\n \n public void setStuSet(Set stuSet) {\n this.stuSet = stuSet;\n }\n \n @Override\n public String toString() {\n return \"Course{\" +\n \"id=\" + id +\n \", courseName='\" + courseName + '\\'' +\n \", courseTime=\" + courseTime +\n '}';\n }\n }\n \n\n## 其所对应的Hibernate映射文件为 Course.hbm.xml\n\n```xml\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n```\n\n---\n\n\n## hibernate.cfg.xml\n\n```xml\n\n\n\n \n \n root\n fangshuoit\n com.mysql.jdbc.Driver\n jdbc:mysql://127.0.0.1:3306/test\n\n \n \n org.hibernate.dialect.MySQL57InnoDBDialect\n \n true\n \n false\n \n \n update\n \n 2\n \n true\n\n \n \n 10\n \n 5\n \n 2\n \n 2000\n \n 2000\n \n 10\n\n \n \n 100\n \n 50\n\n \n true\n org.hibernate.cache.ehcache.EhCacheRegionFactory\n \n \n true\n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n\n\n```\n\n---\n\n## 单元测试\n\n```java\npackage com.em.hibernate;\n\nimport org.hibernate.Session;\nimport org.hibernate.SessionFactory;\nimport org.hibernate.cfg.Configuration;\nimport org.hibernate.service.ServiceRegistry;\nimport org.hibernate.service.ServiceRegistryBuilder;\nimport org.junit.Test;\n\n/**\n * Created by zhangjin on 2017/2/27.\n */\npublic class HibernateTest {\n private static SessionFactory factory;\n\n static {\n// .configuere()方法中参数为hibernate.cfg.xml配置文件位置 不填写表示取src目录下\n Configuration configuration = new Configuration().configure();\n ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()\n .applySettings(configuration.getProperties()).buildServiceRegistry();\n factory = configuration.buildSessionFactory(serviceRegistry);\n }\n \n private Session getSession() {\n return factory.openSession();\n }\n \n @Test\n public void test(){\n //junit运行创建数据库表以及表结构\n }\n}\n```\n","source":"_posts/2017_Hibernate.md","raw":"---\ntitle: Hibernate配置以及实体映射配置\n\ncomments: true \n\ntags: \n - Hibernate\n\ncategories: \n - 框架相关\n\ndescription: Hibernate配置以及配置介绍 + 实体映射文件配置以及配置介绍 + 简单的创建数据库测试 \n\ndate: 2017-02-27\n \n---\n\n# **Hibernate**是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,,它将POJO与数据库表建立映射关系\n# **Hibernate**是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库\n---\n## 下面是测试用实体1---Student.java\n\n\n\n \n package com.em.entity;\n \n /**\n * Created by zhangjin on 2017/2/27.\n */\n public class Student {\n private Integer id;\n private String name;\n private Integer age;\n private Double score;\n \n private Course course;\n \n public Student() {\n }\n \n public Student(String name, Integer age, Double score) {\n this.name = name;\n this.age = age;\n this.score = score;\n }\n \n public Student(String name, Integer age, Double score, Course course) {\n this.name = name;\n this.age = age;\n this.score = score;\n this.course = course;\n }\n \n public Integer getId() {\n return id;\n }\n \n public void setId(Integer id) {\n this.id = id;\n }\n \n public String getName() {\n return name;\n }\n \n public void setName(String name) {\n this.name = name;\n }\n \n public Integer getAge() {\n return age;\n }\n \n public void setAge(Integer age) {\n this.age = age;\n }\n \n public Double getScore() {\n return score;\n }\n \n public void setScore(Double score) {\n this.score = score;\n }\n \n public Course getCourse() {\n return course;\n }\n \n public void setCourse(Course course) {\n this.course = course;\n }\n \n @Override\n public String toString() {\n return \"Student{\" +\n \"id=\" + id +\n \", name='\" + name + '\\'' +\n \", age=\" + age +\n \", course=\" + course +\n '}';\n }\n }\n\n\n## 其所对应的Hibernate映射文件为 Student.hbm.xml\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n :score]]>\n \n \n\n\n---\n## 下面是测试用实体2---Course.java\n \n \n package com.em.entity;\n \n import java.sql.Timestamp;\n import java.util.HashSet;\n import java.util.Set;\n \n /**\n * Created by zhangjin on 2017/2/27.\n */\n \n public class Course {\n private Integer id;\n private String courseName;\n private Timestamp courseTime;\n \n Set stuSet = new HashSet<>();\n \n public Course() {\n }\n \n public Course(String courseName, Timestamp courseTime) {\n this.courseName = courseName;\n this.courseTime = courseTime;\n }\n \n public Integer getId() {\n return id;\n }\n \n public void setId(Integer id) {\n this.id = id;\n }\n \n public String getCourseName() {\n return courseName;\n }\n \n public void setCourseName(String courseName) {\n this.courseName = courseName;\n }\n \n public Timestamp getCourseTime() {\n return courseTime;\n }\n \n public void setCourseTime(Timestamp courseTime) {\n this.courseTime = courseTime;\n }\n \n public Set getStuSet() {\n return stuSet;\n }\n \n public void setStuSet(Set stuSet) {\n this.stuSet = stuSet;\n }\n \n @Override\n public String toString() {\n return \"Course{\" +\n \"id=\" + id +\n \", courseName='\" + courseName + '\\'' +\n \", courseTime=\" + courseTime +\n '}';\n }\n }\n \n\n## 其所对应的Hibernate映射文件为 Course.hbm.xml\n\n```xml\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n```\n\n---\n\n\n## hibernate.cfg.xml\n\n```xml\n\n\n\n \n \n root\n fangshuoit\n com.mysql.jdbc.Driver\n jdbc:mysql://127.0.0.1:3306/test\n\n \n \n org.hibernate.dialect.MySQL57InnoDBDialect\n \n true\n \n false\n \n \n update\n \n 2\n \n true\n\n \n \n 10\n \n 5\n \n 2\n \n 2000\n \n 2000\n \n 10\n\n \n \n 100\n \n 50\n\n \n true\n org.hibernate.cache.ehcache.EhCacheRegionFactory\n \n \n true\n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n\n\n```\n\n---\n\n## 单元测试\n\n```java\npackage com.em.hibernate;\n\nimport org.hibernate.Session;\nimport org.hibernate.SessionFactory;\nimport org.hibernate.cfg.Configuration;\nimport org.hibernate.service.ServiceRegistry;\nimport org.hibernate.service.ServiceRegistryBuilder;\nimport org.junit.Test;\n\n/**\n * Created by zhangjin on 2017/2/27.\n */\npublic class HibernateTest {\n private static SessionFactory factory;\n\n static {\n// .configuere()方法中参数为hibernate.cfg.xml配置文件位置 不填写表示取src目录下\n Configuration configuration = new Configuration().configure();\n ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()\n .applySettings(configuration.getProperties()).buildServiceRegistry();\n factory = configuration.buildSessionFactory(serviceRegistry);\n }\n \n private Session getSession() {\n return factory.openSession();\n }\n \n @Test\n public void test(){\n //junit运行创建数据库表以及表结构\n }\n}\n```\n","slug":"2017_Hibernate","published":1,"updated":"2018-01-13T02:29:22.220Z","layout":"post","photos":[],"link":"","_id":"ckm3invea008224ujzc3aoos6","content":"

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,,它将POJO与数据库表建立映射关系

Hibernate是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库


\n

下面是测试用实体1—Student.java

\n
package com.em.entity;\n\n/**\n * Created by zhangjin on 2017/2/27.\n */\npublic class Student {\n    private Integer id;\n    private String name;\n    private Integer age;\n    private Double score;\n\n    private Course course;\n\n    public Student() {\n    }\n\n    public Student(String name, Integer age, Double score) {\n        this.name = name;\n        this.age = age;\n        this.score = score;\n    }\n\n    public Student(String name, Integer age, Double score, Course course) {\n        this.name = name;\n        this.age = age;\n        this.score = score;\n        this.course = course;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public Integer getAge() {\n        return age;\n    }\n\n    public void setAge(Integer age) {\n        this.age = age;\n    }\n\n    public Double getScore() {\n        return score;\n    }\n\n    public void setScore(Double score) {\n        this.score = score;\n    }\n\n    public Course getCourse() {\n        return course;\n    }\n\n    public void setCourse(Course course) {\n        this.course = course;\n    }\n\n    @Override\n    public String toString() {\n        return "Student{" +\n                "id=" + id +\n                ", name='" + name + '\\'' +\n                ", age=" + age +\n                ", course=" + course +\n                '}';\n    }\n}\n

其所对应的Hibernate映射文件为 Student.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE hibernate-mapping PUBLIC\n        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"\n        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">\n<!-- package:包名-->\n<hibernate-mapping package="com.em.entity">\n    <class name="Student" table="STUDENT">\n        <!--表的主键-->\n        <id name="id" type="java.lang.Integer">\n            <column name="ID"/>\n            <!--主键的生成方式-->\n            <!--    antive:数据库本地的方式(数据库自己生成主键的方式)   increment:Hiernate以递增的方式生成(只测试时候用 因为有并发的问题)\n                    identity:由底层数据库负责生成标识符(数据库必须支持主键自增) sequence:利用底层数据库提供的序列生成标识符\n                    hilo:Hibernate通过高低算法生成标识符\n                     -->\n            <generator class="native"/>\n        </id>\n        <!--属性映射 name:实体属性名-->\n        <!--length:限制长度 但是我测试了好几次都不起作用 无论是String还是Integer-->\n        <property name="name" type="java.lang.String" length="100">\n            <!--column: 数据库中列名-->\n            <column name="NAME"/>\n        </property>\n        <property name="age" type="java.lang.Integer" length="10">\n            <column name="AGE"/>\n        </property>\n        <!--index: 为SCORE列添加索引 索引名:score_index-->\n        <property name="score" type="java.lang.Double" index="score_index">\n            <column name="SCORE"/>\n        </property>\n\n        <!--映射关联关系(即外键) class:外键对应的类名 column:外键列名-->\n        <many-to-one name="course" class="Course" column="COURSE_ID"/>\n\n        <!--匿名查询使用的查询语句(可以把部分查询语句配置在配置文件中 方便修改)-->\n        <query name="findByName"><![CDATA[from Student where name like :name and score > :score]]></query>\n    </class>\n</hibernate-mapping>\n

\n

下面是测试用实体2—Course.java

package com.em.entity;\n\nimport java.sql.Timestamp;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Created by zhangjin on 2017/2/27.\n */\n\npublic class Course {\n    private Integer id;\n    private String courseName;\n    private Timestamp courseTime;\n\n    Set<Student> stuSet = new HashSet<>();\n\n    public Course() {\n    }\n\n    public Course(String courseName, Timestamp courseTime) {\n        this.courseName = courseName;\n        this.courseTime = courseTime;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getCourseName() {\n        return courseName;\n    }\n\n    public void setCourseName(String courseName) {\n        this.courseName = courseName;\n    }\n\n    public Timestamp getCourseTime() {\n        return courseTime;\n    }\n\n    public void setCourseTime(Timestamp courseTime) {\n        this.courseTime = courseTime;\n    }\n\n    public Set<Student> getStuSet() {\n        return stuSet;\n    }\n\n    public void setStuSet(Set<Student> stuSet) {\n        this.stuSet = stuSet;\n    }\n\n    @Override\n    public String toString() {\n        return "Course{" +\n                "id=" + id +\n                ", courseName='" + courseName + '\\'' +\n                ", courseTime=" + courseTime +\n                '}';\n    }\n}\n

其所对应的Hibernate映射文件为 Course.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE hibernate-mapping PUBLIC
\"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"
\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">
<hibernate-mapping package=\"com.em.entity\">
<!--
dynamic-insert:动态插入: 设置插入只插入非空的属性(默认为false)
设置之后insert语句只包含非空的参数,
没有值的字段不会出现在insert语句中
dynamic-update:动态更新: 同上,换成更新(默认false)
动态update对性能有一个重大的影响,就是打开了以后,不同的对象的sql语句会不一样,
如果你一次更新多条记录,hibernate将不能使用 executeBatch进行批量更新,这样效率降低很多。
同时,在这种情况下,多条sql意味着数据库要做多次sql语句编译。
select-before-update 设置在每次更新操作前都查询一次,(默认false)
如果查询的数据库中数据与要更新的实体相同就不会执行更新语句,但是无论是否更新都会先执行查询,效率低
-->
<class name=\"Course\" table=\"COURSE\" dynamic-insert=\"true\" dynamic-update=\"false\" select-before-update=\"false\">
<id name=\"id\" type=\"java.lang.Integer\" length=\"10\">
<column name=\"ID\"/>
<generator class=\"native\"/>
</id>
<property name=\"courseName\" type=\"java.lang.String\" length=\"100\">
<column name=\"COURSE_NAME\"/>
</property>
<property name=\"courseTime\" type=\"java.sql.Timestamp\" index=\"time_index\">
<column name=\"COURSE_TIME\"/>
</property>

<!--映射一对多的集合属性-->
<!--属性:fetch
select(默认): 延迟检索
join : 迫切采用做外连接的方式初始化n关联的1的一端的属性
属性:lazy
proxy : 延迟检索
false : 立即检索
属性:inverse:设置true的一方(Set端设置)放弃维护关联关系,可减少维护次数,减少内存消耗
属性:cascade:设置级联操作(默认none)
-->
<set name=\"stuSet\" inverse=\"true\" fetch=\"select\">
<key column=\"COURSE_ID\"/><!--多的一端的数据库外键名-->
<one-to-many class=\"Student\"/><!--一对多对应的类-->
</set>
</class>
</hibernate-mapping>
\n
\n

hibernate.cfg.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE hibernate-configuration PUBLIC
\"-//Hibernate/Hibernate Configuration DTD 3.0//EN\"
\"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd\">
<hibernate-configuration>
<session-factory>
<!--连接数据库配置-->
<property name=\"hibernate.connection.username\">root</property>
<property name=\"hibernate.connection.password\">fangshuoit</property>
<property name=\"hibernate.connection.driver_class\">com.mysql.jdbc.Driver</property>
<property name=\"hibernate.connection.url\">jdbc:mysql://127.0.0.1:3306/test</property>

<!--hibernate信息配置-->
<!--hibernate使用的数据库方言-->
<property name=\"hibernate.dialect\">org.hibernate.dialect.MySQL57InnoDBDialect</property>
<!--操作时是否在控制台打印sql-->
<property name=\"show_sql\">true</property>
<!--是否对sql进行格式化-->
<property name=\"format_sql\">false</property>
<!--指定自动生成数据表的策略-->
<!--create:每次重新生成数据表 create-drop:每次重新生成表,SessionFactory关闭就删表
update:每次只是更新,不改变数据 validate:不一样就抛异常,不修改表-->
<property name=\"hbm2ddl.auto\">update</property>
<!--设置事务隔离级别-->
<property name=\"connection.isolation\">2</property>
<!--设置删除对象后 使其OID置为null-->
<property name=\"use_identifier_rollback\">true</property>

<!--c3p0连接池配置-->
<!--连接池最大连接数-->
<property name=\"c3p0.max_size\">10</property>
<!--连接池最小连接数-->
<property name=\"c3p0.min_size\">5</property>
<!--连接池的连接耗尽时,一次向再获取多少个数据库连接-->
<property name=\"c3p0.acquire_increment\">2</property>
<!--连接对象在多久未使用,会被销毁 2s-->
<property name=\"c3p0.idle_test_period\">2000</property>
<!--多久时间检测一次连接超时情况-->
<property name=\"c3p0.timeout\">2000</property>
<!--缓存Statement对象数量-->
<property name=\"c3p0.max_statements\">10</property>

<!--mysql无效 oracle有效-->
<!--设定jdbc的Statement读取数据的时候每次从数据库中取出多少记录条数-->
<property name=\"jdbc.fetch_size\">100</property>
<!--设定对数据库进行批量操作(增删改)时 一次操作的条数-->
<property name=\"jdbc.batch_size\">50</property>

<!--启用二级缓存-->
<property name=\"cache.use_second_level_cache\">true</property>
<property name=\"hibernate.cache.region.factory_class\">org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
<!--启用查询缓存-->
<property name=\"cache.use_query_cache\">true</property>

<!--
配置管理session的方式 就是配置session绑定到某一运行环境
(将getCurrentSession()返回的session绑定到当前运行线程中 此session的上下文是thread)
-->
<!--注意:Spring3.x不能为thread,否则报错:org.hibernate.HibernateException: save is not valid without active transaction ,
以上配置在 增加、删除、修改 操作时,都能正确执行,事务也正常执行!
当执行 查询 操作时,不需要事务的支持,问题来了,报错:org.hibernate.HibernateException: No Session found for current thread
意思是必须在transcation.isActive()条件下才能执行,
可以解决办法是:当方法不需要事务支持的时候,使用 Session session = sessionFactory.openSession()来获得Session对象,问题解决!
-->
<!--<property name=\"current_session_context_class\">thread</property>-->

<!--指定关联的.hbm.xml文件-->
<mapping resource=\"com/em/entity/Student.hbm.xml\"/>
<mapping resource=\"com/em/entity/Course.hbm.xml\"/>

<!--设置使用二级缓存的类(类级别的二级缓存) 以及使用二级缓存的策略usage-->
<class-cache class=\"com.em.entity.Student\" usage=\"read-write\"/>
<class-cache class=\"com.em.entity.Course\" usage=\"read-write\"/>
<!--设置使用二级缓存的类(集合级别的二级缓存) 以及使用二级缓存的策略usage-->
<collection-cache collection=\"com.em.entity.Course.stuSet\" usage=\"read-write\"/>
</session-factory>

</hibernate-configuration>
\n
\n

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.em.hibernate;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.Test;

/**
* Created by zhangjin on 2017/2/27.
*/
public class HibernateTest {
private static SessionFactory factory;

static {
// .configuere()方法中参数为hibernate.cfg.xml配置文件位置 不填写表示取src目录下
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties()).buildServiceRegistry();
factory = configuration.buildSessionFactory(serviceRegistry);
}

private Session getSession() {
return factory.openSession();
}

@Test
public void test(){
//junit运行创建数据库表以及表结构
}
}
\n","site":{"data":{}},"excerpt":"

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,,它将POJO与数据库表建立映射关系

Hibernate是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库


\n

下面是测试用实体1—Student.java

","more":"
package com.em.entity;\n\n/**\n * Created by zhangjin on 2017/2/27.\n */\npublic class Student {\n    private Integer id;\n    private String name;\n    private Integer age;\n    private Double score;\n\n    private Course course;\n\n    public Student() {\n    }\n\n    public Student(String name, Integer age, Double score) {\n        this.name = name;\n        this.age = age;\n        this.score = score;\n    }\n\n    public Student(String name, Integer age, Double score, Course course) {\n        this.name = name;\n        this.age = age;\n        this.score = score;\n        this.course = course;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public Integer getAge() {\n        return age;\n    }\n\n    public void setAge(Integer age) {\n        this.age = age;\n    }\n\n    public Double getScore() {\n        return score;\n    }\n\n    public void setScore(Double score) {\n        this.score = score;\n    }\n\n    public Course getCourse() {\n        return course;\n    }\n\n    public void setCourse(Course course) {\n        this.course = course;\n    }\n\n    @Override\n    public String toString() {\n        return "Student{" +\n                "id=" + id +\n                ", name='" + name + '\\'' +\n                ", age=" + age +\n                ", course=" + course +\n                '}';\n    }\n}\n

其所对应的Hibernate映射文件为 Student.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE hibernate-mapping PUBLIC\n        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"\n        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">\n<!-- package:包名-->\n<hibernate-mapping package="com.em.entity">\n    <class name="Student" table="STUDENT">\n        <!--表的主键-->\n        <id name="id" type="java.lang.Integer">\n            <column name="ID"/>\n            <!--主键的生成方式-->\n            <!--    antive:数据库本地的方式(数据库自己生成主键的方式)   increment:Hiernate以递增的方式生成(只测试时候用 因为有并发的问题)\n                    identity:由底层数据库负责生成标识符(数据库必须支持主键自增) sequence:利用底层数据库提供的序列生成标识符\n                    hilo:Hibernate通过高低算法生成标识符\n                     -->\n            <generator class="native"/>\n        </id>\n        <!--属性映射 name:实体属性名-->\n        <!--length:限制长度 但是我测试了好几次都不起作用 无论是String还是Integer-->\n        <property name="name" type="java.lang.String" length="100">\n            <!--column: 数据库中列名-->\n            <column name="NAME"/>\n        </property>\n        <property name="age" type="java.lang.Integer" length="10">\n            <column name="AGE"/>\n        </property>\n        <!--index: 为SCORE列添加索引 索引名:score_index-->\n        <property name="score" type="java.lang.Double" index="score_index">\n            <column name="SCORE"/>\n        </property>\n\n        <!--映射关联关系(即外键) class:外键对应的类名 column:外键列名-->\n        <many-to-one name="course" class="Course" column="COURSE_ID"/>\n\n        <!--匿名查询使用的查询语句(可以把部分查询语句配置在配置文件中 方便修改)-->\n        <query name="findByName"><![CDATA[from Student where name like :name and score > :score]]></query>\n    </class>\n</hibernate-mapping>\n

\n

下面是测试用实体2—Course.java

package com.em.entity;\n\nimport java.sql.Timestamp;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Created by zhangjin on 2017/2/27.\n */\n\npublic class Course {\n    private Integer id;\n    private String courseName;\n    private Timestamp courseTime;\n\n    Set<Student> stuSet = new HashSet<>();\n\n    public Course() {\n    }\n\n    public Course(String courseName, Timestamp courseTime) {\n        this.courseName = courseName;\n        this.courseTime = courseTime;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getCourseName() {\n        return courseName;\n    }\n\n    public void setCourseName(String courseName) {\n        this.courseName = courseName;\n    }\n\n    public Timestamp getCourseTime() {\n        return courseTime;\n    }\n\n    public void setCourseTime(Timestamp courseTime) {\n        this.courseTime = courseTime;\n    }\n\n    public Set<Student> getStuSet() {\n        return stuSet;\n    }\n\n    public void setStuSet(Set<Student> stuSet) {\n        this.stuSet = stuSet;\n    }\n\n    @Override\n    public String toString() {\n        return "Course{" +\n                "id=" + id +\n                ", courseName='" + courseName + '\\'' +\n                ", courseTime=" + courseTime +\n                '}';\n    }\n}\n

其所对应的Hibernate映射文件为 Course.hbm.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE hibernate-mapping PUBLIC
\"-//Hibernate/Hibernate Mapping DTD 3.0//EN\"
\"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd\">
<hibernate-mapping package=\"com.em.entity\">
<!--
dynamic-insert:动态插入: 设置插入只插入非空的属性(默认为false)
设置之后insert语句只包含非空的参数,
没有值的字段不会出现在insert语句中
dynamic-update:动态更新: 同上,换成更新(默认false)
动态update对性能有一个重大的影响,就是打开了以后,不同的对象的sql语句会不一样,
如果你一次更新多条记录,hibernate将不能使用 executeBatch进行批量更新,这样效率降低很多。
同时,在这种情况下,多条sql意味着数据库要做多次sql语句编译。
select-before-update 设置在每次更新操作前都查询一次,(默认false)
如果查询的数据库中数据与要更新的实体相同就不会执行更新语句,但是无论是否更新都会先执行查询,效率低
-->
<class name=\"Course\" table=\"COURSE\" dynamic-insert=\"true\" dynamic-update=\"false\" select-before-update=\"false\">
<id name=\"id\" type=\"java.lang.Integer\" length=\"10\">
<column name=\"ID\"/>
<generator class=\"native\"/>
</id>
<property name=\"courseName\" type=\"java.lang.String\" length=\"100\">
<column name=\"COURSE_NAME\"/>
</property>
<property name=\"courseTime\" type=\"java.sql.Timestamp\" index=\"time_index\">
<column name=\"COURSE_TIME\"/>
</property>

<!--映射一对多的集合属性-->
<!--属性:fetch
select(默认): 延迟检索
join : 迫切采用做外连接的方式初始化n关联的1的一端的属性
属性:lazy
proxy : 延迟检索
false : 立即检索
属性:inverse:设置true的一方(Set端设置)放弃维护关联关系,可减少维护次数,减少内存消耗
属性:cascade:设置级联操作(默认none)
-->
<set name=\"stuSet\" inverse=\"true\" fetch=\"select\">
<key column=\"COURSE_ID\"/><!--多的一端的数据库外键名-->
<one-to-many class=\"Student\"/><!--一对多对应的类-->
</set>
</class>
</hibernate-mapping>
\n
\n

hibernate.cfg.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE hibernate-configuration PUBLIC
\"-//Hibernate/Hibernate Configuration DTD 3.0//EN\"
\"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd\">
<hibernate-configuration>
<session-factory>
<!--连接数据库配置-->
<property name=\"hibernate.connection.username\">root</property>
<property name=\"hibernate.connection.password\">fangshuoit</property>
<property name=\"hibernate.connection.driver_class\">com.mysql.jdbc.Driver</property>
<property name=\"hibernate.connection.url\">jdbc:mysql://127.0.0.1:3306/test</property>

<!--hibernate信息配置-->
<!--hibernate使用的数据库方言-->
<property name=\"hibernate.dialect\">org.hibernate.dialect.MySQL57InnoDBDialect</property>
<!--操作时是否在控制台打印sql-->
<property name=\"show_sql\">true</property>
<!--是否对sql进行格式化-->
<property name=\"format_sql\">false</property>
<!--指定自动生成数据表的策略-->
<!--create:每次重新生成数据表 create-drop:每次重新生成表,SessionFactory关闭就删表
update:每次只是更新,不改变数据 validate:不一样就抛异常,不修改表-->
<property name=\"hbm2ddl.auto\">update</property>
<!--设置事务隔离级别-->
<property name=\"connection.isolation\">2</property>
<!--设置删除对象后 使其OID置为null-->
<property name=\"use_identifier_rollback\">true</property>

<!--c3p0连接池配置-->
<!--连接池最大连接数-->
<property name=\"c3p0.max_size\">10</property>
<!--连接池最小连接数-->
<property name=\"c3p0.min_size\">5</property>
<!--连接池的连接耗尽时,一次向再获取多少个数据库连接-->
<property name=\"c3p0.acquire_increment\">2</property>
<!--连接对象在多久未使用,会被销毁 2s-->
<property name=\"c3p0.idle_test_period\">2000</property>
<!--多久时间检测一次连接超时情况-->
<property name=\"c3p0.timeout\">2000</property>
<!--缓存Statement对象数量-->
<property name=\"c3p0.max_statements\">10</property>

<!--mysql无效 oracle有效-->
<!--设定jdbc的Statement读取数据的时候每次从数据库中取出多少记录条数-->
<property name=\"jdbc.fetch_size\">100</property>
<!--设定对数据库进行批量操作(增删改)时 一次操作的条数-->
<property name=\"jdbc.batch_size\">50</property>

<!--启用二级缓存-->
<property name=\"cache.use_second_level_cache\">true</property>
<property name=\"hibernate.cache.region.factory_class\">org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
<!--启用查询缓存-->
<property name=\"cache.use_query_cache\">true</property>

<!--
配置管理session的方式 就是配置session绑定到某一运行环境
(将getCurrentSession()返回的session绑定到当前运行线程中 此session的上下文是thread)
-->
<!--注意:Spring3.x不能为thread,否则报错:org.hibernate.HibernateException: save is not valid without active transaction ,
以上配置在 增加、删除、修改 操作时,都能正确执行,事务也正常执行!
当执行 查询 操作时,不需要事务的支持,问题来了,报错:org.hibernate.HibernateException: No Session found for current thread
意思是必须在transcation.isActive()条件下才能执行,
可以解决办法是:当方法不需要事务支持的时候,使用 Session session = sessionFactory.openSession()来获得Session对象,问题解决!
-->
<!--<property name=\"current_session_context_class\">thread</property>-->

<!--指定关联的.hbm.xml文件-->
<mapping resource=\"com/em/entity/Student.hbm.xml\"/>
<mapping resource=\"com/em/entity/Course.hbm.xml\"/>

<!--设置使用二级缓存的类(类级别的二级缓存) 以及使用二级缓存的策略usage-->
<class-cache class=\"com.em.entity.Student\" usage=\"read-write\"/>
<class-cache class=\"com.em.entity.Course\" usage=\"read-write\"/>
<!--设置使用二级缓存的类(集合级别的二级缓存) 以及使用二级缓存的策略usage-->
<collection-cache collection=\"com.em.entity.Course.stuSet\" usage=\"read-write\"/>
</session-factory>

</hibernate-configuration>
\n
\n

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.em.hibernate;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.Test;

/**
* Created by zhangjin on 2017/2/27.
*/
public class HibernateTest {
private static SessionFactory factory;

static {
// .configuere()方法中参数为hibernate.cfg.xml配置文件位置 不填写表示取src目录下
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties()).buildServiceRegistry();
factory = configuration.buildSessionFactory(serviceRegistry);
}

private Session getSession() {
return factory.openSession();
}

@Test
public void test(){
//junit运行创建数据库表以及表结构
}
}
"},{"title":"Mongodb的基础操作详解","comments":1,"description":"Mongodb的增删改查,索引操作","date":"2017-02-28T16:00:00.000Z","_content":"\n## 对数据库的操作\n\n >show dbs 显示所有数据库\n >use admin 切换数据库(数据库可以自动新建)\n >db.auth('username','password') 验证用户权限\n >db.dropDatabase() 删除数据库\n\n## 对集合(collection)的操作(也就是对表的操作)\n\n >db.集合名.drop() 删除表\n >show collections 显示所有的collection\n >show tables 显示所有的collection\n\n\n\n\n## 对数据的操作\n\n### 查询\n\n >db.集合名.find() \t查询数据库中某个集合的数据\n >db.集合名.find({x:1}) \t查询数据库 查询包含参数json字符串的数据\n >db.集合名.find().count() \t查询数据库中数据的个数(可带参数 表条件)\n >db.集合名.find().skip(5).limit(10).sort({x:1,y:1})\t\t查询条件:skip:跳过数据的个数;limit:查询条数;sort:按照某个字段排序(这里是先按x排序,再按y排序, 1表示正像排序,-1代表反向)\n >db.集合名.find({y:{$exists:true}}) //查询y字段存在的数据\n >db.集合名.find({x:1}).explain() //带详细信息的查询\n\n\n### 增加\n\n >db.集合名.insert({x:1}) \t插入数据(参数格式为json格式,集合名可以自动新建) 插入时会自动生成id(名叫\"_id\"的字段)\n >db.集合名.insert({x:1, _id:1}) \t带id的插入(需要保证_id全局唯一,不可重复)\n >for(i=0;i<100;i++) \tdb.集合名.insert({x:i+200}) 使用循环插入数据库\n\n### 更新\n\n Update有4个参数:(默认更新一条数据)\n 第一个selector(查询条件),\n 第二个newValue(要更新的数据), $set、$inc、$push\n 第三个upserts, true:查到就更新,没查到就新建\n 第四个multipleUpdate, true:批量更新\n\n\n > db.集合名.update({x:10},{x:100})\t\t更新方法: 第一个json:更新条件 ;第二个json:更新结果 (把x=1的数据更新为x=10)\n > db.集合名.update({x:10},$set:{y:100}})\t部分更新: 只更新第二个json所改变的字段(不加set操作符,会把原来的所有的字段覆盖为更新结果)\n > db.集合名.update({x:10},{x:100}, true) 更新或插入: 如果更新的数据不存在就插入一条数据(upsert = true)\n > db.集合名.update({x:10},$set:{y:100}}, false, true) 批量更新: 为防止误操作,必须使用set操作,(upsert = false,)\n\n\n### 删除\n\n \n (默认删除查到的所有数据)\n > db.集合名.remove({x:200}) 删除操作:必须有参数\n\n\n\n## 索引\n\n### 查看索引\n\n >db.collection.getIndexes() //每个collection都会创建默认的一个索引 _id\n\n### 创建索引\n\n\n >db.collection.ensureIndex({x:1}) 1表示正像排序,-1代表反向 (最好在存在大量数据之前就已经添加好索引)\n \n >db.collection.ensureIndex({x:1},{name:\"\"my_index}) 自定义命名索引名\n\n\n### 删除索引\n\n >db.collection.dropIndex(\"index_name\") //参数是索引名\n\n### 索引类型 \n\n 1. _id索引(默认的唯一索引)\n 2.单键索引: 普通索引(值为一个字符串,数字,日期等) db.collection.ensureIndex({x:1})\n 3.多键索引: 当索引的字段为数组时,创建出的索引称为多key索引\n \n 如果存在一个多值的字段如: { \"_id\" : ObjectId(\"58bbcfeb48e281d3bc2a95b9\"), \"x\" : [ 1, 2, 3, 4, 5, 6 ] }\n 那么在这条记录创建时候 会自动建立多键索引(或手动创建 db.collection.createIndex( {x: 1} ) )\n 查询: db.collection.find({x: 1}) 或者 db.collection.find({x: 3})\n \n \n 4.复合索引: 相当于多个单键索引 创建方式: db.collection.ensureIndex({x:1,y:1})\n 5.过期索引: (TTL) 一段时间后会过期(删除数据)的索引 (过期时间 单位是秒)\n 创建方式: db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n 注意: 1.存储在过期索引字段的值必须是ISODate或ISODate数组,不能是其他,否则无法删除\n 2.如果是Date数组就按照时间从小到大删\n 3.不能是复合索引\n 4.删除不是准时的,而是60s的一个定时进程执行的删除\n 5.全文索引\n db.collection.ensureIndex({\"key\":\"text\"})\n db.collection.ensureIndex({\"key\":\"text\",\"key2\":\"text\"})\n db.collection.ensureIndex({\"$**\":\"text\"})\n \n*其中key表示原来的\"字段名\",键; text表示的不是原来的正序或倒序,而是要检索的内容(创建索引要写\"text\")*\n*一个collection只能创建一个全文索引*\n\n 全文索引测试:\n 创建内容: > db.test.insert({\"x\":\"aaa ccc\"})\n > db.test.insert({\"x\":\"bbb\"})\n > db.test.insert({\"y\":\"ccc\"})\n > db.test.insert({\"z\":\"ccc\"})\n > db.test.insert({\"x\":\"aaa111\"})\n > db.test.insert({\"y\":\"aaa111\"})\n > db.test.insert({\"x\":\"aaa222\",\"y\":\"ccc\"})\n 查看创建的内容: db.test.find() //6条\n 创建索引: db.test.ensureIndex({\"x\":\"text\"})\n 查看所有索引:db.test.getIndexes() //2个,第一个是默认的索引\n 查询: \n > db.test.find({$text:{$search:\"aaa\"}}) //没找到数据\n \n > db.test.find({$text:{$search:\"aaa111\"}}) //查到一条(全部匹配)\n { \"_id\" : ObjectId(\"58bcb92118bc95b8f476def1\"), \"x\" : \"aaa111\" }\n \n > db.test.find({$text:{$search:\"ccc\"}})\n { \"_id\" : ObjectId(\"58bf535ae830c0c5f89055e7\"), \"x\" : \"aaa ccc\" }\n \n > db.test.find({$text:{$search:\"aaa111 aaa\"}}) //或查询 用户空格分开\n { \"_id\" : ObjectId(\"58bf535ae830c0c5f89055e7\"), \"x\" : \"aaa ccc\" }\n { \"_id\" : ObjectId(\"58bcb92118bc95b8f476def1\"), \"x\" : \"aaa111\" }\n \n > db.test.find({$text:{$search:\"aaa111 aaa -ccc\"}}) //或查询,其中'-'表示不包含 \n { \"_id\" : ObjectId(\"58bcb92118bc95b8f476def1\"), \"x\" : \"aaa111\" }\n \n > db.test.find({$text:{$search:\"\\\"aaa\\\" \\\"aaa111\\\"\"}}) //与查询 1条\n > db.test.find({$text:{$search:\"\\\"aaa222\\\"\"}}) //1条\n > db.test.find({$text:{$search:\"\\\"aaa\\\" \\\"aa\\\"\"}}) //1条\n > db.test.find({$text:{$search:\"\\\"aaa\\\" \\\"aa\\\"\"}}) //1条\n > db.test.find({$text:{$search:\"\\\"aaa222\\\" \\\"aa\\\"\"}}) //1条\n \n *全文索引相似度查询 返回查询到的结果与要查询的数据的相似度\n {score:{$meta:\"textScore\"}} 得到的数越大表示相似度越高\n \n > db.test.find({$text:{$search:\"aaa222\"}},{score:{$meta:\"textScore\"}})\n { \"_id\" : ObjectId(\"58bcb92518bc95b8f476def2\"), \"x\" : \"aaa222\", \"y\" : \"ccc\", \"score\" : 1.1 }\n > db.test.find({$text:{$search:\"aaa\"}},{score:{$meta:\"textScore\"}})\n { \"_id\" : ObjectId(\"58bf535ae830c0c5f89055e7\"), \"x\" : \"aaa ccc\", \"score\" : 0.75 }\n \n //用相似度排序\n > db.test.find({$text:{$search:\"bbb\"}},{score:{$meta:\"textScore\"}}).sort({score:{$meta:\"textScore\"}})\n \n \n *全文索引限制:\n 1.一次只能指定一个$text查询\n 2.$text不能使用$nor查询\n 3.查询中包含$text,则hint(强制指定索引)无效\n 4.目前已经支持中文查询(版本3.0.6)\n \n 6.地理位置索引 \n 1. 2D索引:平面地理位置索引\n 创建索引: db.collection.ensureIndex({w:\"2d\"})\n 位置表示方法:经纬度[经度, 纬度] //经度:-180 -> 180; 纬度:-90 -> 90\n \n 1.插入数据(插入数据超过范围时可能会有不可预知的错误)\n > db.mytest4.find()\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n { \"_id\" : ObjectId(\"58bfa6aabf18b69568aedc5e\"), \"w\" : [ 10, 30 ] }\n { \"_id\" : ObjectId(\"58bfa780bf18b69568aedc64\"), \"w\" : [ 180, 90 ] }\n { \"_id\" : ObjectId(\"58bfa723bf18b69568aedc62\"), \"w\" : [ -100, 90 ] }\n { \"_id\" : ObjectId(\"58bfa728bf18b69568aedc63\"), \"w\" : [ -150, 90 ] }\n 2.查询\n 1.普通查询\n > db.mytest4.find({w:{$near:[1,1]}}) //默认返回100个距离所求点最近的点的位置\n \n 2.查询某个距离内的点\n > db.mytest4.find({w:{$near:[1,1],$maxDistance:10}}) //$maxDistance设置最远距离(直线距离)(不支持$minDistance)\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n \n 3.查询某个形状范围内的点\n 1.矩形: $geoWithin + $box (查询[0,0],[100,10]内的点)\n > db.mytest4.find({w:{$geoWithin:{$box:[[0,0],[100,10]]}}})\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n \n 2.圆形: $geoWithin + $center (查询圆心为[0,0],半径为140内的点)\n > db.mytest4.find({w:{$geoWithin:{$center:[[0,0],140]}}})\n { \"_id\" : ObjectId(\"58bfa723bf18b69568aedc62\"), \"w\" : [ -100, 90 ] }\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n { \"_id\" : ObjectId(\"58bfa6aabf18b69568aedc5e\"), \"w\" : [ 10, 30 ] }\n \n 3.多边形: $geoWithin + $polygon (查询这几个点围成的多边形内的点,写至少是三个点)\n > db.mytest4.find({w:{$geoWithin:{$polygon:[[0,0],[80,91],[-45,70]]}}})\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n { \"_id\" : ObjectId(\"58bfa6aabf18b69568aedc5e\"), \"w\" : [ 10, 30 ] }\n \n #查询方式2 \n geoNear:要查询的collection名;\n near:基点;\n minDistance:搜索的最小距离;\n maxDistance:搜索的最大距离;\n num:查询数量\n > db.runCommand({geoNear:\"mytest4\",near:[1,5],maxDistance:10,num:2})\n {\n \"results\" : [\n {\n \"dis\" : 2,\n \"obj\" : {\n \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"),\n \"w\" : [\n 1,\n 3\n ]\n }\n },\n {\n \"dis\" : 4,\n \"obj\" : {\n \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"),\n \"w\" : [\n 1,\n 1\n ]\n }\n }\n ],\n \"stats\" : {\n \"nscanned\" : 3, \n \"objectsLoaded\" : 2,\n \"avgDistance\" : 3, \n \"maxDistance\" : 4,\n \"time\" : 0 \n }, \n \"ok\" : 1\n }\n \n \n 2. 2Dsphere索引:球面地理位置索引:geoNear查询 使用runCommand命令进行使用\n 1. 创建\n db.collection.ensureIndex({w:\"2dsphere\"})\n 位置表示方法:GeoJSON:可以描述一个点,线或各种形状等 {type:\"\",coordinates:[]}\n 2. 查询\n \n //待续... \n \n \n### 索引属性\n\n 1.name: db.collection.ensureIndex({x:1},{name:\"my_index\"}) 指定索引名称\n 2.unique: db.collection.ensureIndex({y:1},{unique:true}) 设置为true,则不允许在同一个collection中插入有相同唯一索引的字段(索引的数值不能重复)\n 注意:如果插入的数据没有指定的索引字段,则只能插入一条这样的数据,再插入则会报错(相当于重复)\n 3.sparse: db.collection.ensureIndex({y:1},{sparse:true}) 设置为true,则不会在没有的字段的数据上创建索引\n > db.collection.find({y:{$exists:true}}) //查询y字段存在的数据\n\n\n 测试: 先插入6条数据,其中1条包含有y字段 \n > db.test3.ensureIndex({y:1},{name:\"mytest_y\"},{sparse:false}) //创建索引\n > db.test3.find({y:{$exists:false}}) //查询不存在y的数据 5条(数据库并没有为这5条数据建立y的索引,而是数据库优化的结果,这里查询并没有用上面的索引,so查到了)\n { \"_id\" : ObjectId(\"58bf69ced52555dec4840312\"), \"x\" : 1 }\n { \"_id\" : ObjectId(\"58bf6c00d52555dec4840319\"), \"x\" : 2111313, \"k\" : 1 }\n { \"_id\" : ObjectId(\"58bf6c13d52555dec484031a\"), \"x\" : 2111313, \"k\" : 2 }\n { \"_id\" : ObjectId(\"58bf6c15d52555dec484031b\"), \"x\" : 2111313, \"k\" : 3 }\n { \"_id\" : ObjectId(\"58bf6c19d52555dec484031c\"), \"x\" : 2111313, \"z\" : 3 }\n > db.test3.find({y:{$exists:false}}).hint(\"mytest_y\") //强制指定索引,使用指定的索引查询,就无法查到不包含y的数据\n > //没有查到数据\n\n 4.expireAfterSeconds 是否定时删除TTL(过期索引) \n > db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n \n### 其他\n\n 1. mongodb可以通过profile来监控数据,进行优化。\n \n 查看当前是否开启profile功能用命令\n > db.getProfilingLevel()\n 0\n \n 开启profile功能 \n > db.setProfilingLevel(2) //0代表关闭,1代表记录慢命令,2代表全部\n { \"was\" : 0, \"slowms\" : 100, \"ok\" : 1 }\n \n 查看当前的监控日志\n db.system.profile.find() \n\n ","source":"_posts/2017_Mongodb_curd.md","raw":"---\ntitle: Mongodb的基础操作详解\n\ncomments: true \n\ntags: \n - Mongodb\n - CRUD\n\ncategories: \n - 数据库\n\ndescription: Mongodb的增删改查,索引操作\n\ndate: 2017-03-1\n \n---\n\n## 对数据库的操作\n\n >show dbs 显示所有数据库\n >use admin 切换数据库(数据库可以自动新建)\n >db.auth('username','password') 验证用户权限\n >db.dropDatabase() 删除数据库\n\n## 对集合(collection)的操作(也就是对表的操作)\n\n >db.集合名.drop() 删除表\n >show collections 显示所有的collection\n >show tables 显示所有的collection\n\n\n\n\n## 对数据的操作\n\n### 查询\n\n >db.集合名.find() \t查询数据库中某个集合的数据\n >db.集合名.find({x:1}) \t查询数据库 查询包含参数json字符串的数据\n >db.集合名.find().count() \t查询数据库中数据的个数(可带参数 表条件)\n >db.集合名.find().skip(5).limit(10).sort({x:1,y:1})\t\t查询条件:skip:跳过数据的个数;limit:查询条数;sort:按照某个字段排序(这里是先按x排序,再按y排序, 1表示正像排序,-1代表反向)\n >db.集合名.find({y:{$exists:true}}) //查询y字段存在的数据\n >db.集合名.find({x:1}).explain() //带详细信息的查询\n\n\n### 增加\n\n >db.集合名.insert({x:1}) \t插入数据(参数格式为json格式,集合名可以自动新建) 插入时会自动生成id(名叫\"_id\"的字段)\n >db.集合名.insert({x:1, _id:1}) \t带id的插入(需要保证_id全局唯一,不可重复)\n >for(i=0;i<100;i++) \tdb.集合名.insert({x:i+200}) 使用循环插入数据库\n\n### 更新\n\n Update有4个参数:(默认更新一条数据)\n 第一个selector(查询条件),\n 第二个newValue(要更新的数据), $set、$inc、$push\n 第三个upserts, true:查到就更新,没查到就新建\n 第四个multipleUpdate, true:批量更新\n\n\n > db.集合名.update({x:10},{x:100})\t\t更新方法: 第一个json:更新条件 ;第二个json:更新结果 (把x=1的数据更新为x=10)\n > db.集合名.update({x:10},$set:{y:100}})\t部分更新: 只更新第二个json所改变的字段(不加set操作符,会把原来的所有的字段覆盖为更新结果)\n > db.集合名.update({x:10},{x:100}, true) 更新或插入: 如果更新的数据不存在就插入一条数据(upsert = true)\n > db.集合名.update({x:10},$set:{y:100}}, false, true) 批量更新: 为防止误操作,必须使用set操作,(upsert = false,)\n\n\n### 删除\n\n \n (默认删除查到的所有数据)\n > db.集合名.remove({x:200}) 删除操作:必须有参数\n\n\n\n## 索引\n\n### 查看索引\n\n >db.collection.getIndexes() //每个collection都会创建默认的一个索引 _id\n\n### 创建索引\n\n\n >db.collection.ensureIndex({x:1}) 1表示正像排序,-1代表反向 (最好在存在大量数据之前就已经添加好索引)\n \n >db.collection.ensureIndex({x:1},{name:\"\"my_index}) 自定义命名索引名\n\n\n### 删除索引\n\n >db.collection.dropIndex(\"index_name\") //参数是索引名\n\n### 索引类型 \n\n 1. _id索引(默认的唯一索引)\n 2.单键索引: 普通索引(值为一个字符串,数字,日期等) db.collection.ensureIndex({x:1})\n 3.多键索引: 当索引的字段为数组时,创建出的索引称为多key索引\n \n 如果存在一个多值的字段如: { \"_id\" : ObjectId(\"58bbcfeb48e281d3bc2a95b9\"), \"x\" : [ 1, 2, 3, 4, 5, 6 ] }\n 那么在这条记录创建时候 会自动建立多键索引(或手动创建 db.collection.createIndex( {x: 1} ) )\n 查询: db.collection.find({x: 1}) 或者 db.collection.find({x: 3})\n \n \n 4.复合索引: 相当于多个单键索引 创建方式: db.collection.ensureIndex({x:1,y:1})\n 5.过期索引: (TTL) 一段时间后会过期(删除数据)的索引 (过期时间 单位是秒)\n 创建方式: db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n 注意: 1.存储在过期索引字段的值必须是ISODate或ISODate数组,不能是其他,否则无法删除\n 2.如果是Date数组就按照时间从小到大删\n 3.不能是复合索引\n 4.删除不是准时的,而是60s的一个定时进程执行的删除\n 5.全文索引\n db.collection.ensureIndex({\"key\":\"text\"})\n db.collection.ensureIndex({\"key\":\"text\",\"key2\":\"text\"})\n db.collection.ensureIndex({\"$**\":\"text\"})\n \n*其中key表示原来的\"字段名\",键; text表示的不是原来的正序或倒序,而是要检索的内容(创建索引要写\"text\")*\n*一个collection只能创建一个全文索引*\n\n 全文索引测试:\n 创建内容: > db.test.insert({\"x\":\"aaa ccc\"})\n > db.test.insert({\"x\":\"bbb\"})\n > db.test.insert({\"y\":\"ccc\"})\n > db.test.insert({\"z\":\"ccc\"})\n > db.test.insert({\"x\":\"aaa111\"})\n > db.test.insert({\"y\":\"aaa111\"})\n > db.test.insert({\"x\":\"aaa222\",\"y\":\"ccc\"})\n 查看创建的内容: db.test.find() //6条\n 创建索引: db.test.ensureIndex({\"x\":\"text\"})\n 查看所有索引:db.test.getIndexes() //2个,第一个是默认的索引\n 查询: \n > db.test.find({$text:{$search:\"aaa\"}}) //没找到数据\n \n > db.test.find({$text:{$search:\"aaa111\"}}) //查到一条(全部匹配)\n { \"_id\" : ObjectId(\"58bcb92118bc95b8f476def1\"), \"x\" : \"aaa111\" }\n \n > db.test.find({$text:{$search:\"ccc\"}})\n { \"_id\" : ObjectId(\"58bf535ae830c0c5f89055e7\"), \"x\" : \"aaa ccc\" }\n \n > db.test.find({$text:{$search:\"aaa111 aaa\"}}) //或查询 用户空格分开\n { \"_id\" : ObjectId(\"58bf535ae830c0c5f89055e7\"), \"x\" : \"aaa ccc\" }\n { \"_id\" : ObjectId(\"58bcb92118bc95b8f476def1\"), \"x\" : \"aaa111\" }\n \n > db.test.find({$text:{$search:\"aaa111 aaa -ccc\"}}) //或查询,其中'-'表示不包含 \n { \"_id\" : ObjectId(\"58bcb92118bc95b8f476def1\"), \"x\" : \"aaa111\" }\n \n > db.test.find({$text:{$search:\"\\\"aaa\\\" \\\"aaa111\\\"\"}}) //与查询 1条\n > db.test.find({$text:{$search:\"\\\"aaa222\\\"\"}}) //1条\n > db.test.find({$text:{$search:\"\\\"aaa\\\" \\\"aa\\\"\"}}) //1条\n > db.test.find({$text:{$search:\"\\\"aaa\\\" \\\"aa\\\"\"}}) //1条\n > db.test.find({$text:{$search:\"\\\"aaa222\\\" \\\"aa\\\"\"}}) //1条\n \n *全文索引相似度查询 返回查询到的结果与要查询的数据的相似度\n {score:{$meta:\"textScore\"}} 得到的数越大表示相似度越高\n \n > db.test.find({$text:{$search:\"aaa222\"}},{score:{$meta:\"textScore\"}})\n { \"_id\" : ObjectId(\"58bcb92518bc95b8f476def2\"), \"x\" : \"aaa222\", \"y\" : \"ccc\", \"score\" : 1.1 }\n > db.test.find({$text:{$search:\"aaa\"}},{score:{$meta:\"textScore\"}})\n { \"_id\" : ObjectId(\"58bf535ae830c0c5f89055e7\"), \"x\" : \"aaa ccc\", \"score\" : 0.75 }\n \n //用相似度排序\n > db.test.find({$text:{$search:\"bbb\"}},{score:{$meta:\"textScore\"}}).sort({score:{$meta:\"textScore\"}})\n \n \n *全文索引限制:\n 1.一次只能指定一个$text查询\n 2.$text不能使用$nor查询\n 3.查询中包含$text,则hint(强制指定索引)无效\n 4.目前已经支持中文查询(版本3.0.6)\n \n 6.地理位置索引 \n 1. 2D索引:平面地理位置索引\n 创建索引: db.collection.ensureIndex({w:\"2d\"})\n 位置表示方法:经纬度[经度, 纬度] //经度:-180 -> 180; 纬度:-90 -> 90\n \n 1.插入数据(插入数据超过范围时可能会有不可预知的错误)\n > db.mytest4.find()\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n { \"_id\" : ObjectId(\"58bfa6aabf18b69568aedc5e\"), \"w\" : [ 10, 30 ] }\n { \"_id\" : ObjectId(\"58bfa780bf18b69568aedc64\"), \"w\" : [ 180, 90 ] }\n { \"_id\" : ObjectId(\"58bfa723bf18b69568aedc62\"), \"w\" : [ -100, 90 ] }\n { \"_id\" : ObjectId(\"58bfa728bf18b69568aedc63\"), \"w\" : [ -150, 90 ] }\n 2.查询\n 1.普通查询\n > db.mytest4.find({w:{$near:[1,1]}}) //默认返回100个距离所求点最近的点的位置\n \n 2.查询某个距离内的点\n > db.mytest4.find({w:{$near:[1,1],$maxDistance:10}}) //$maxDistance设置最远距离(直线距离)(不支持$minDistance)\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n \n 3.查询某个形状范围内的点\n 1.矩形: $geoWithin + $box (查询[0,0],[100,10]内的点)\n > db.mytest4.find({w:{$geoWithin:{$box:[[0,0],[100,10]]}}})\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n \n 2.圆形: $geoWithin + $center (查询圆心为[0,0],半径为140内的点)\n > db.mytest4.find({w:{$geoWithin:{$center:[[0,0],140]}}})\n { \"_id\" : ObjectId(\"58bfa723bf18b69568aedc62\"), \"w\" : [ -100, 90 ] }\n { \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"), \"w\" : [ 1, 1 ] }\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n { \"_id\" : ObjectId(\"58bfa6aabf18b69568aedc5e\"), \"w\" : [ 10, 30 ] }\n \n 3.多边形: $geoWithin + $polygon (查询这几个点围成的多边形内的点,写至少是三个点)\n > db.mytest4.find({w:{$geoWithin:{$polygon:[[0,0],[80,91],[-45,70]]}}})\n { \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"), \"w\" : [ 1, 3 ] }\n { \"_id\" : ObjectId(\"58bfa6aabf18b69568aedc5e\"), \"w\" : [ 10, 30 ] }\n \n #查询方式2 \n geoNear:要查询的collection名;\n near:基点;\n minDistance:搜索的最小距离;\n maxDistance:搜索的最大距离;\n num:查询数量\n > db.runCommand({geoNear:\"mytest4\",near:[1,5],maxDistance:10,num:2})\n {\n \"results\" : [\n {\n \"dis\" : 2,\n \"obj\" : {\n \"_id\" : ObjectId(\"58bfa6a1bf18b69568aedc5d\"),\n \"w\" : [\n 1,\n 3\n ]\n }\n },\n {\n \"dis\" : 4,\n \"obj\" : {\n \"_id\" : ObjectId(\"58bfa69dbf18b69568aedc5c\"),\n \"w\" : [\n 1,\n 1\n ]\n }\n }\n ],\n \"stats\" : {\n \"nscanned\" : 3, \n \"objectsLoaded\" : 2,\n \"avgDistance\" : 3, \n \"maxDistance\" : 4,\n \"time\" : 0 \n }, \n \"ok\" : 1\n }\n \n \n 2. 2Dsphere索引:球面地理位置索引:geoNear查询 使用runCommand命令进行使用\n 1. 创建\n db.collection.ensureIndex({w:\"2dsphere\"})\n 位置表示方法:GeoJSON:可以描述一个点,线或各种形状等 {type:\"\",coordinates:[]}\n 2. 查询\n \n //待续... \n \n \n### 索引属性\n\n 1.name: db.collection.ensureIndex({x:1},{name:\"my_index\"}) 指定索引名称\n 2.unique: db.collection.ensureIndex({y:1},{unique:true}) 设置为true,则不允许在同一个collection中插入有相同唯一索引的字段(索引的数值不能重复)\n 注意:如果插入的数据没有指定的索引字段,则只能插入一条这样的数据,再插入则会报错(相当于重复)\n 3.sparse: db.collection.ensureIndex({y:1},{sparse:true}) 设置为true,则不会在没有的字段的数据上创建索引\n > db.collection.find({y:{$exists:true}}) //查询y字段存在的数据\n\n\n 测试: 先插入6条数据,其中1条包含有y字段 \n > db.test3.ensureIndex({y:1},{name:\"mytest_y\"},{sparse:false}) //创建索引\n > db.test3.find({y:{$exists:false}}) //查询不存在y的数据 5条(数据库并没有为这5条数据建立y的索引,而是数据库优化的结果,这里查询并没有用上面的索引,so查到了)\n { \"_id\" : ObjectId(\"58bf69ced52555dec4840312\"), \"x\" : 1 }\n { \"_id\" : ObjectId(\"58bf6c00d52555dec4840319\"), \"x\" : 2111313, \"k\" : 1 }\n { \"_id\" : ObjectId(\"58bf6c13d52555dec484031a\"), \"x\" : 2111313, \"k\" : 2 }\n { \"_id\" : ObjectId(\"58bf6c15d52555dec484031b\"), \"x\" : 2111313, \"k\" : 3 }\n { \"_id\" : ObjectId(\"58bf6c19d52555dec484031c\"), \"x\" : 2111313, \"z\" : 3 }\n > db.test3.find({y:{$exists:false}}).hint(\"mytest_y\") //强制指定索引,使用指定的索引查询,就无法查到不包含y的数据\n > //没有查到数据\n\n 4.expireAfterSeconds 是否定时删除TTL(过期索引) \n > db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n \n### 其他\n\n 1. mongodb可以通过profile来监控数据,进行优化。\n \n 查看当前是否开启profile功能用命令\n > db.getProfilingLevel()\n 0\n \n 开启profile功能 \n > db.setProfilingLevel(2) //0代表关闭,1代表记录慢命令,2代表全部\n { \"was\" : 0, \"slowms\" : 100, \"ok\" : 1 }\n \n 查看当前的监控日志\n db.system.profile.find() \n\n ","slug":"2017_Mongodb_curd","published":1,"updated":"2018-01-13T02:29:22.229Z","layout":"post","photos":[],"link":"","_id":"ckm3inveb008524ujkox0mwh6","content":"

对数据库的操作

>show dbs   显示所有数据库\n>use admin 切换数据库(数据库可以自动新建)\n>db.auth('username','password') 验证用户权限\n>db.dropDatabase() 删除数据库\n

对集合(collection)的操作(也就是对表的操作)

>db.集合名.drop() 删除表\n>show collections 显示所有的collection\n>show tables  显示所有的collection\n
\n

对数据的操作

查询

>db.集合名.find()      查询数据库中某个集合的数据\n>db.集合名.find({x:1})      查询数据库 查询包含参数json字符串的数据\n>db.集合名.find().count()      查询数据库中数据的个数(可带参数 表条件)\n>db.集合名.find().skip(5).limit(10).sort({x:1,y:1})        查询条件:skip:跳过数据的个数;limit:查询条数;sort:按照某个字段排序(这里是先按x排序,再按y排序, 1表示正像排序,-1代表反向)\n>db.集合名.find({y:{$exists:true}})  //查询y字段存在的数据\n>db.集合名.find({x:1}).explain()       //带详细信息的查询\n

增加

>db.集合名.insert({x:1})     插入数据(参数格式为json格式,集合名可以自动新建) 插入时会自动生成id(名叫"_id"的字段)\n>db.集合名.insert({x:1, _id:1})     带id的插入(需要保证_id全局唯一,不可重复)\n>for(i=0;i<100;i++)     db.集合名.insert({x:i+200}) 使用循环插入数据库\n

更新

Update有4个参数:(默认更新一条数据)\n        第一个selector(查询条件),\n        第二个newValue(要更新的数据), $set、$inc、$push\n        第三个upserts, true:查到就更新,没查到就新建\n        第四个multipleUpdate,  true:批量更新\n\n\n> db.集合名.update({x:10},{x:100})        更新方法: 第一个json:更新条件 ;第二个json:更新结果 (把x=1的数据更新为x=10)\n> db.集合名.update({x:10},$set:{y:100}})    部分更新: 只更新第二个json所改变的字段(不加set操作符,会把原来的所有的字段覆盖为更新结果)\n> db.集合名.update({x:10},{x:100}, true)  更新或插入: 如果更新的数据不存在就插入一条数据(upsert = true)\n> db.集合名.update({x:10},$set:{y:100}}, false, true) 批量更新: 为防止误操作,必须使用set操作,(upsert = false,)\n

删除

(默认删除查到的所有数据)\n> db.集合名.remove({x:200}) 删除操作:必须有参数\n

索引

查看索引

>db.collection.getIndexes()     //每个collection都会创建默认的一个索引 _id\n

创建索引

>db.collection.ensureIndex({x:1})  1表示正像排序,-1代表反向 (最好在存在大量数据之前就已经添加好索引)\n\n>db.collection.ensureIndex({x:1},{name:""my_index})  自定义命名索引名\n

删除索引

>db.collection.dropIndex("index_name")  //参数是索引名\n

索引类型

1. _id索引(默认的唯一索引)\n2.单键索引: 普通索引(值为一个字符串,数字,日期等) db.collection.ensureIndex({x:1})\n3.多键索引: 当索引的字段为数组时,创建出的索引称为多key索引\n\n如果存在一个多值的字段如: { "_id" : ObjectId("58bbcfeb48e281d3bc2a95b9"), "x" : [ 1, 2, 3, 4, 5, 6 ] }\n那么在这条记录创建时候 会自动建立多键索引(或手动创建 db.collection.createIndex( {x: 1} )  )\n查询: db.collection.find({x: 1}) 或者  db.collection.find({x: 3})\n\n\n4.复合索引: 相当于多个单键索引 创建方式: db.collection.ensureIndex({x:1,y:1})\n5.过期索引: (TTL) 一段时间后会过期(删除数据)的索引 (过期时间 单位是秒)\n    创建方式: db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n    注意:   1.存储在过期索引字段的值必须是ISODate或ISODate数组,不能是其他,否则无法删除\n            2.如果是Date数组就按照时间从小到大删\n            3.不能是复合索引\n            4.删除不是准时的,而是60s的一个定时进程执行的删除\n            5.全文索引\n                db.collection.ensureIndex({"key":"text"})\n                db.collection.ensureIndex({"key":"text","key2":"text"})\n                db.collection.ensureIndex({"$**":"text"})\n

其中key表示原来的”字段名”,键; text表示的不是原来的正序或倒序,而是要检索的内容(创建索引要写”text”)
一个collection只能创建一个全文索引

\n
全文索引测试:\n    创建内容:   > db.test.insert({"x":"aaa ccc"})\n               > db.test.insert({"x":"bbb"})\n               > db.test.insert({"y":"ccc"})\n               > db.test.insert({"z":"ccc"})\n               > db.test.insert({"x":"aaa111"})\n               > db.test.insert({"y":"aaa111"})\n               > db.test.insert({"x":"aaa222","y":"ccc"})\n    查看创建的内容:  db.test.find()   //6条\n    创建索引:   db.test.ensureIndex({"x":"text"})\n    查看所有索引:db.test.getIndexes()    //2个,第一个是默认的索引\n    查询:       \n                > db.test.find({$text:{$search:"aaa"}})           //没找到数据\n\n                > db.test.find({$text:{$search:"aaa111"}})        //查到一条(全部匹配)\n                { "_id" : ObjectId("58bcb92118bc95b8f476def1"), "x" : "aaa111" }\n\n                > db.test.find({$text:{$search:"ccc"}})\n                { "_id" : ObjectId("58bf535ae830c0c5f89055e7"), "x" : "aaa ccc" }\n\n                > db.test.find({$text:{$search:"aaa111 aaa"}})    //或查询 用户空格分开\n                { "_id" : ObjectId("58bf535ae830c0c5f89055e7"), "x" : "aaa ccc" }\n                { "_id" : ObjectId("58bcb92118bc95b8f476def1"), "x" : "aaa111" }\n\n                > db.test.find({$text:{$search:"aaa111 aaa -ccc"}})  //或查询,其中'-'表示不包含 \n                { "_id" : ObjectId("58bcb92118bc95b8f476def1"), "x" : "aaa111" }\n\n                > db.test.find({$text:{$search:"\\"aaa\\" \\"aaa111\\""}}) //与查询 1条\n                > db.test.find({$text:{$search:"\\"aaa222\\""}})          //1条\n                > db.test.find({$text:{$search:"\\"aaa\\" \\"aa\\""}})      //1条\n                > db.test.find({$text:{$search:"\\"aaa\\" \\"aa\\""}})      //1条\n                > db.test.find({$text:{$search:"\\"aaa222\\" \\"aa\\""}})   //1条\n\n            *全文索引相似度查询 返回查询到的结果与要查询的数据的相似度\n                {score:{$meta:"textScore"}}   得到的数越大表示相似度越高\n\n                > db.test.find({$text:{$search:"aaa222"}},{score:{$meta:"textScore"}})\n                { "_id" : ObjectId("58bcb92518bc95b8f476def2"), "x" : "aaa222", "y" : "ccc", "score" : 1.1 }\n                > db.test.find({$text:{$search:"aaa"}},{score:{$meta:"textScore"}})\n                { "_id" : ObjectId("58bf535ae830c0c5f89055e7"), "x" : "aaa ccc", "score" : 0.75 }\n\n                //用相似度排序\n                > db.test.find({$text:{$search:"bbb"}},{score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}})\n\n\n            *全文索引限制:\n                1.一次只能指定一个$text查询\n                2.$text不能使用$nor查询\n                3.查询中包含$text,则hint(强制指定索引)无效\n                4.目前已经支持中文查询(版本3.0.6)\n\n            6.地理位置索引 \n                1. 2D索引:平面地理位置索引\n                    创建索引: db.collection.ensureIndex({w:"2d"})\n                        位置表示方法:经纬度[经度, 纬度]    //经度:-180 -> 180; 纬度:-90 -> 90\n\n    1.插入数据(插入数据超过范围时可能会有不可预知的错误)\n        > db.mytest4.find()\n        { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n        { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n        { "_id" : ObjectId("58bfa6aabf18b69568aedc5e"), "w" : [ 10, 30 ] }\n        { "_id" : ObjectId("58bfa780bf18b69568aedc64"), "w" : [ 180, 90 ] }\n        { "_id" : ObjectId("58bfa723bf18b69568aedc62"), "w" : [ -100, 90 ] }\n        { "_id" : ObjectId("58bfa728bf18b69568aedc63"), "w" : [ -150, 90 ] }\n    2.查询\n        1.普通查询\n        > db.mytest4.find({w:{$near:[1,1]}})   //默认返回100个距离所求点最近的点的位置\n\n        2.查询某个距离内的点\n        > db.mytest4.find({w:{$near:[1,1],$maxDistance:10}})  //$maxDistance设置最远距离(直线距离)(不支持$minDistance)\n        { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n        { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n\n        3.查询某个形状范围内的点\n            1.矩形: $geoWithin + $box (查询[0,0],[100,10]内的点)\n                > db.mytest4.find({w:{$geoWithin:{$box:[[0,0],[100,10]]}}})\n                { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n                { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n\n            2.圆形: $geoWithin + $center (查询圆心为[0,0],半径为140内的点)\n                > db.mytest4.find({w:{$geoWithin:{$center:[[0,0],140]}}})\n                { "_id" : ObjectId("58bfa723bf18b69568aedc62"), "w" : [ -100, 90 ] }\n                { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n                { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n                { "_id" : ObjectId("58bfa6aabf18b69568aedc5e"), "w" : [ 10, 30 ] }\n\n            3.多边形: $geoWithin + $polygon (查询这几个点围成的多边形内的点,写至少是三个点)\n                > db.mytest4.find({w:{$geoWithin:{$polygon:[[0,0],[80,91],[-45,70]]}}})\n                { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n                { "_id" : ObjectId("58bfa6aabf18b69568aedc5e"), "w" : [ 10, 30 ] }\n\n    #查询方式2   \n        geoNear:要查询的collection名;\n        near:基点;\n        minDistance:搜索的最小距离;\n        maxDistance:搜索的最大距离;\n        num:查询数量\n            > db.runCommand({geoNear:"mytest4",near:[1,5],maxDistance:10,num:2})\n            {\n                "results" : [\n                    {\n                        "dis" : 2,\n                        "obj" : {\n                            "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"),\n                            "w" : [\n                                1,\n                                3\n                            ]\n                        }\n                    },\n                    {\n                        "dis" : 4,\n                        "obj" : {\n                            "_id" : ObjectId("58bfa69dbf18b69568aedc5c"),\n                            "w" : [\n                                1,\n                                1\n                            ]\n                        }\n                    }\n                ],\n                "stats" : {\n                    "nscanned" : 3,       \n                    "objectsLoaded" : 2,\n                    "avgDistance" : 3,    \n                    "maxDistance" : 4,\n                    "time" : 0              \n                }, \n                "ok" : 1\n            }\n\n\n                2. 2Dsphere索引:球面地理位置索引:geoNear查询 使用runCommand命令进行使用\n                    1. 创建\n                        db.collection.ensureIndex({w:"2dsphere"})\n                            位置表示方法:GeoJSON:可以描述一个点,线或各种形状等 {type:"",coordinates:[<coordinates>]}\n                    2. 查询\n\n                 //待续... \n

索引属性

1.name:   db.collection.ensureIndex({x:1},{name:"my_index"}) 指定索引名称\n2.unique: db.collection.ensureIndex({y:1},{unique:true})  设置为true,则不允许在同一个collection中插入有相同唯一索引的字段(索引的数值不能重复)\n    注意:如果插入的数据没有指定的索引字段,则只能插入一条这样的数据,再插入则会报错(相当于重复)\n3.sparse: db.collection.ensureIndex({y:1},{sparse:true})  设置为true,则不会在没有的字段的数据上创建索引\n        > db.collection.find({y:{$exists:true}})  //查询y字段存在的数据\n\n\n    测试: 先插入6条数据,其中1条包含有y字段 \n        > db.test3.ensureIndex({y:1},{name:"mytest_y"},{sparse:false})  //创建索引\n        > db.test3.find({y:{$exists:false}})   //查询不存在y的数据 5条(数据库并没有为这5条数据建立y的索引,而是数据库优化的结果,这里查询并没有用上面的索引,so查到了)\n        { "_id" : ObjectId("58bf69ced52555dec4840312"), "x" : 1 }\n        { "_id" : ObjectId("58bf6c00d52555dec4840319"), "x" : 2111313, "k" : 1 }\n        { "_id" : ObjectId("58bf6c13d52555dec484031a"), "x" : 2111313, "k" : 2 }\n        { "_id" : ObjectId("58bf6c15d52555dec484031b"), "x" : 2111313, "k" : 3 }\n        { "_id" : ObjectId("58bf6c19d52555dec484031c"), "x" : 2111313, "z" : 3 }\n        > db.test3.find({y:{$exists:false}}).hint("mytest_y")     //强制指定索引,使用指定的索引查询,就无法查到不包含y的数据\n        >                                                         //没有查到数据\n\n4.expireAfterSeconds 是否定时删除TTL(过期索引) \n        > db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n

其他

1. mongodb可以通过profile来监控数据,进行优化。\n\n    查看当前是否开启profile功能用命令\n    > db.getProfilingLevel()\n    0\n\n    开启profile功能 \n    > db.setProfilingLevel(2)  //0代表关闭,1代表记录慢命令,2代表全部\n    { "was" : 0, "slowms" : 100, "ok" : 1 }\n\n    查看当前的监控日志\n    db.system.profile.find() \n
","site":{"data":{}},"excerpt":"

对数据库的操作

>show dbs   显示所有数据库\n>use admin 切换数据库(数据库可以自动新建)\n>db.auth('username','password') 验证用户权限\n>db.dropDatabase() 删除数据库\n

对集合(collection)的操作(也就是对表的操作)

>db.集合名.drop() 删除表\n>show collections 显示所有的collection\n>show tables  显示所有的collection\n
","more":"

对数据的操作

查询

>db.集合名.find()      查询数据库中某个集合的数据\n>db.集合名.find({x:1})      查询数据库 查询包含参数json字符串的数据\n>db.集合名.find().count()      查询数据库中数据的个数(可带参数 表条件)\n>db.集合名.find().skip(5).limit(10).sort({x:1,y:1})        查询条件:skip:跳过数据的个数;limit:查询条数;sort:按照某个字段排序(这里是先按x排序,再按y排序, 1表示正像排序,-1代表反向)\n>db.集合名.find({y:{$exists:true}})  //查询y字段存在的数据\n>db.集合名.find({x:1}).explain()       //带详细信息的查询\n

增加

>db.集合名.insert({x:1})     插入数据(参数格式为json格式,集合名可以自动新建) 插入时会自动生成id(名叫"_id"的字段)\n>db.集合名.insert({x:1, _id:1})     带id的插入(需要保证_id全局唯一,不可重复)\n>for(i=0;i<100;i++)     db.集合名.insert({x:i+200}) 使用循环插入数据库\n

更新

Update有4个参数:(默认更新一条数据)\n        第一个selector(查询条件),\n        第二个newValue(要更新的数据), $set、$inc、$push\n        第三个upserts, true:查到就更新,没查到就新建\n        第四个multipleUpdate,  true:批量更新\n\n\n> db.集合名.update({x:10},{x:100})        更新方法: 第一个json:更新条件 ;第二个json:更新结果 (把x=1的数据更新为x=10)\n> db.集合名.update({x:10},$set:{y:100}})    部分更新: 只更新第二个json所改变的字段(不加set操作符,会把原来的所有的字段覆盖为更新结果)\n> db.集合名.update({x:10},{x:100}, true)  更新或插入: 如果更新的数据不存在就插入一条数据(upsert = true)\n> db.集合名.update({x:10},$set:{y:100}}, false, true) 批量更新: 为防止误操作,必须使用set操作,(upsert = false,)\n

删除

(默认删除查到的所有数据)\n> db.集合名.remove({x:200}) 删除操作:必须有参数\n

索引

查看索引

>db.collection.getIndexes()     //每个collection都会创建默认的一个索引 _id\n

创建索引

>db.collection.ensureIndex({x:1})  1表示正像排序,-1代表反向 (最好在存在大量数据之前就已经添加好索引)\n\n>db.collection.ensureIndex({x:1},{name:""my_index})  自定义命名索引名\n

删除索引

>db.collection.dropIndex("index_name")  //参数是索引名\n

索引类型

1. _id索引(默认的唯一索引)\n2.单键索引: 普通索引(值为一个字符串,数字,日期等) db.collection.ensureIndex({x:1})\n3.多键索引: 当索引的字段为数组时,创建出的索引称为多key索引\n\n如果存在一个多值的字段如: { "_id" : ObjectId("58bbcfeb48e281d3bc2a95b9"), "x" : [ 1, 2, 3, 4, 5, 6 ] }\n那么在这条记录创建时候 会自动建立多键索引(或手动创建 db.collection.createIndex( {x: 1} )  )\n查询: db.collection.find({x: 1}) 或者  db.collection.find({x: 3})\n\n\n4.复合索引: 相当于多个单键索引 创建方式: db.collection.ensureIndex({x:1,y:1})\n5.过期索引: (TTL) 一段时间后会过期(删除数据)的索引 (过期时间 单位是秒)\n    创建方式: db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n    注意:   1.存储在过期索引字段的值必须是ISODate或ISODate数组,不能是其他,否则无法删除\n            2.如果是Date数组就按照时间从小到大删\n            3.不能是复合索引\n            4.删除不是准时的,而是60s的一个定时进程执行的删除\n            5.全文索引\n                db.collection.ensureIndex({"key":"text"})\n                db.collection.ensureIndex({"key":"text","key2":"text"})\n                db.collection.ensureIndex({"$**":"text"})\n

其中key表示原来的”字段名”,键; text表示的不是原来的正序或倒序,而是要检索的内容(创建索引要写”text”)
一个collection只能创建一个全文索引

\n
全文索引测试:\n    创建内容:   > db.test.insert({"x":"aaa ccc"})\n               > db.test.insert({"x":"bbb"})\n               > db.test.insert({"y":"ccc"})\n               > db.test.insert({"z":"ccc"})\n               > db.test.insert({"x":"aaa111"})\n               > db.test.insert({"y":"aaa111"})\n               > db.test.insert({"x":"aaa222","y":"ccc"})\n    查看创建的内容:  db.test.find()   //6条\n    创建索引:   db.test.ensureIndex({"x":"text"})\n    查看所有索引:db.test.getIndexes()    //2个,第一个是默认的索引\n    查询:       \n                > db.test.find({$text:{$search:"aaa"}})           //没找到数据\n\n                > db.test.find({$text:{$search:"aaa111"}})        //查到一条(全部匹配)\n                { "_id" : ObjectId("58bcb92118bc95b8f476def1"), "x" : "aaa111" }\n\n                > db.test.find({$text:{$search:"ccc"}})\n                { "_id" : ObjectId("58bf535ae830c0c5f89055e7"), "x" : "aaa ccc" }\n\n                > db.test.find({$text:{$search:"aaa111 aaa"}})    //或查询 用户空格分开\n                { "_id" : ObjectId("58bf535ae830c0c5f89055e7"), "x" : "aaa ccc" }\n                { "_id" : ObjectId("58bcb92118bc95b8f476def1"), "x" : "aaa111" }\n\n                > db.test.find({$text:{$search:"aaa111 aaa -ccc"}})  //或查询,其中'-'表示不包含 \n                { "_id" : ObjectId("58bcb92118bc95b8f476def1"), "x" : "aaa111" }\n\n                > db.test.find({$text:{$search:"\\"aaa\\" \\"aaa111\\""}}) //与查询 1条\n                > db.test.find({$text:{$search:"\\"aaa222\\""}})          //1条\n                > db.test.find({$text:{$search:"\\"aaa\\" \\"aa\\""}})      //1条\n                > db.test.find({$text:{$search:"\\"aaa\\" \\"aa\\""}})      //1条\n                > db.test.find({$text:{$search:"\\"aaa222\\" \\"aa\\""}})   //1条\n\n            *全文索引相似度查询 返回查询到的结果与要查询的数据的相似度\n                {score:{$meta:"textScore"}}   得到的数越大表示相似度越高\n\n                > db.test.find({$text:{$search:"aaa222"}},{score:{$meta:"textScore"}})\n                { "_id" : ObjectId("58bcb92518bc95b8f476def2"), "x" : "aaa222", "y" : "ccc", "score" : 1.1 }\n                > db.test.find({$text:{$search:"aaa"}},{score:{$meta:"textScore"}})\n                { "_id" : ObjectId("58bf535ae830c0c5f89055e7"), "x" : "aaa ccc", "score" : 0.75 }\n\n                //用相似度排序\n                > db.test.find({$text:{$search:"bbb"}},{score:{$meta:"textScore"}}).sort({score:{$meta:"textScore"}})\n\n\n            *全文索引限制:\n                1.一次只能指定一个$text查询\n                2.$text不能使用$nor查询\n                3.查询中包含$text,则hint(强制指定索引)无效\n                4.目前已经支持中文查询(版本3.0.6)\n\n            6.地理位置索引 \n                1. 2D索引:平面地理位置索引\n                    创建索引: db.collection.ensureIndex({w:"2d"})\n                        位置表示方法:经纬度[经度, 纬度]    //经度:-180 -> 180; 纬度:-90 -> 90\n\n    1.插入数据(插入数据超过范围时可能会有不可预知的错误)\n        > db.mytest4.find()\n        { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n        { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n        { "_id" : ObjectId("58bfa6aabf18b69568aedc5e"), "w" : [ 10, 30 ] }\n        { "_id" : ObjectId("58bfa780bf18b69568aedc64"), "w" : [ 180, 90 ] }\n        { "_id" : ObjectId("58bfa723bf18b69568aedc62"), "w" : [ -100, 90 ] }\n        { "_id" : ObjectId("58bfa728bf18b69568aedc63"), "w" : [ -150, 90 ] }\n    2.查询\n        1.普通查询\n        > db.mytest4.find({w:{$near:[1,1]}})   //默认返回100个距离所求点最近的点的位置\n\n        2.查询某个距离内的点\n        > db.mytest4.find({w:{$near:[1,1],$maxDistance:10}})  //$maxDistance设置最远距离(直线距离)(不支持$minDistance)\n        { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n        { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n\n        3.查询某个形状范围内的点\n            1.矩形: $geoWithin + $box (查询[0,0],[100,10]内的点)\n                > db.mytest4.find({w:{$geoWithin:{$box:[[0,0],[100,10]]}}})\n                { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n                { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n\n            2.圆形: $geoWithin + $center (查询圆心为[0,0],半径为140内的点)\n                > db.mytest4.find({w:{$geoWithin:{$center:[[0,0],140]}}})\n                { "_id" : ObjectId("58bfa723bf18b69568aedc62"), "w" : [ -100, 90 ] }\n                { "_id" : ObjectId("58bfa69dbf18b69568aedc5c"), "w" : [ 1, 1 ] }\n                { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n                { "_id" : ObjectId("58bfa6aabf18b69568aedc5e"), "w" : [ 10, 30 ] }\n\n            3.多边形: $geoWithin + $polygon (查询这几个点围成的多边形内的点,写至少是三个点)\n                > db.mytest4.find({w:{$geoWithin:{$polygon:[[0,0],[80,91],[-45,70]]}}})\n                { "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"), "w" : [ 1, 3 ] }\n                { "_id" : ObjectId("58bfa6aabf18b69568aedc5e"), "w" : [ 10, 30 ] }\n\n    #查询方式2   \n        geoNear:要查询的collection名;\n        near:基点;\n        minDistance:搜索的最小距离;\n        maxDistance:搜索的最大距离;\n        num:查询数量\n            > db.runCommand({geoNear:"mytest4",near:[1,5],maxDistance:10,num:2})\n            {\n                "results" : [\n                    {\n                        "dis" : 2,\n                        "obj" : {\n                            "_id" : ObjectId("58bfa6a1bf18b69568aedc5d"),\n                            "w" : [\n                                1,\n                                3\n                            ]\n                        }\n                    },\n                    {\n                        "dis" : 4,\n                        "obj" : {\n                            "_id" : ObjectId("58bfa69dbf18b69568aedc5c"),\n                            "w" : [\n                                1,\n                                1\n                            ]\n                        }\n                    }\n                ],\n                "stats" : {\n                    "nscanned" : 3,       \n                    "objectsLoaded" : 2,\n                    "avgDistance" : 3,    \n                    "maxDistance" : 4,\n                    "time" : 0              \n                }, \n                "ok" : 1\n            }\n\n\n                2. 2Dsphere索引:球面地理位置索引:geoNear查询 使用runCommand命令进行使用\n                    1. 创建\n                        db.collection.ensureIndex({w:"2dsphere"})\n                            位置表示方法:GeoJSON:可以描述一个点,线或各种形状等 {type:"",coordinates:[<coordinates>]}\n                    2. 查询\n\n                 //待续... \n

索引属性

1.name:   db.collection.ensureIndex({x:1},{name:"my_index"}) 指定索引名称\n2.unique: db.collection.ensureIndex({y:1},{unique:true})  设置为true,则不允许在同一个collection中插入有相同唯一索引的字段(索引的数值不能重复)\n    注意:如果插入的数据没有指定的索引字段,则只能插入一条这样的数据,再插入则会报错(相当于重复)\n3.sparse: db.collection.ensureIndex({y:1},{sparse:true})  设置为true,则不会在没有的字段的数据上创建索引\n        > db.collection.find({y:{$exists:true}})  //查询y字段存在的数据\n\n\n    测试: 先插入6条数据,其中1条包含有y字段 \n        > db.test3.ensureIndex({y:1},{name:"mytest_y"},{sparse:false})  //创建索引\n        > db.test3.find({y:{$exists:false}})   //查询不存在y的数据 5条(数据库并没有为这5条数据建立y的索引,而是数据库优化的结果,这里查询并没有用上面的索引,so查到了)\n        { "_id" : ObjectId("58bf69ced52555dec4840312"), "x" : 1 }\n        { "_id" : ObjectId("58bf6c00d52555dec4840319"), "x" : 2111313, "k" : 1 }\n        { "_id" : ObjectId("58bf6c13d52555dec484031a"), "x" : 2111313, "k" : 2 }\n        { "_id" : ObjectId("58bf6c15d52555dec484031b"), "x" : 2111313, "k" : 3 }\n        { "_id" : ObjectId("58bf6c19d52555dec484031c"), "x" : 2111313, "z" : 3 }\n        > db.test3.find({y:{$exists:false}}).hint("mytest_y")     //强制指定索引,使用指定的索引查询,就无法查到不包含y的数据\n        >                                                         //没有查到数据\n\n4.expireAfterSeconds 是否定时删除TTL(过期索引) \n        > db.collection.ensureIndex({x:1},{expireAfterSeconds:10})\n

其他

1. mongodb可以通过profile来监控数据,进行优化。\n\n    查看当前是否开启profile功能用命令\n    > db.getProfilingLevel()\n    0\n\n    开启profile功能 \n    > db.setProfilingLevel(2)  //0代表关闭,1代表记录慢命令,2代表全部\n    { "was" : 0, "slowms" : 100, "ok" : 1 }\n\n    查看当前的监控日志\n    db.system.profile.find() \n
"},{"title":"jaxp解析xml文档实现增删改查","comments":1,"description":"jaxp解析xml文档","date":"2017-02-04T16:00:00.000Z","_content":"\n\n## 这是被解析的示例xml\n\n```xml\n\n<书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n\n```\n\n**增加**\n\n\n\n```java\npackage cn.xml;\n\nimport java.io.FileOutputStream;\nimport java.io.OutputStream;\n\nimport javax.xml.crypto.dsig.Transform;\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.transform.*;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\n\npublic class Demo03_xml_add {\n //4.向XML文档中添加节点 <售价>12元\n public void add() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n //在内存中的xml中创造一个节点\n Element price = document.createElement(\"售价\");//得到标签\n price.setTextContent(\"12元\");//添加标签中内容\n\n //先得到书节点 转换为标签\n Element book = (Element)document.getElementsByTagName(\"书\").item(0);\n //把创造的节点添加到xml文档中书标签下\n book.appendChild(price);\n //此时只更新了内存中的XML文档(document对象指向的) 所以要把document对象指向的内存中的xml文档更新到硬盘中的xml文档\n\n //更新后的写入XML文档\n //创建工厂实例\n TransformerFactory tf = TransformerFactory.newInstance();\n //通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)\n Transformer tr = tf.newTransformer();\n //DOMSource是Source的实现类 把Document类型封装为Source类型\n Source s = new DOMSource(document);\n //声明输出流对象 指向硬盘中的XML文件\n OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");\n //把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件\n Result r = new StreamResult(f);\n //transform方法(来源, 目的地) 把s写入r\n tr.transform(s, r);\n }\n\n //5.向XML文档中指定位置添加节点 <售价>999元\n public void add2() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n Element price = document.createElement(\"售价\");\n price.setTextContent(\"999元\");\n\n //得到参考节点 即下一个标签(Element是Node的一个子集 Element是Node的扩展)\n Element refNode = (Element) document.getElementsByTagName(\"售价\").item(1);\n\n //得到要插入的节点\n Element book = (Element) document.getElementsByTagName(\"书\").item(0); \n //插入book节点的指定位置 把price节点插入refNode节点之前\n book.insertBefore(price, refNode);//参数也可以是标签 \n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n\n //6.向XML文档中添加属性\n public void add3() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n //得到节点\n Element bookname = (Element) document.getElementsByTagName(\"书名\").item(0);\n bookname.setAttribute(\"name\", \"XXX\");\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n}\n```\n\n**删除**\n\n```java\npackage cn.xml;\n\nimport java.io.FileOutputStream;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\n\npublic class Demo03_xml_delete {\n public void delete() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n //得到要删除的节点\n Element price = (Element) document.getElementsByTagName(\"售价\").item(2);\n //得到要删除节点的父节点\n Element book = (Element) document.getElementsByTagName(\"书\").item(1);\n //通过父节点删除子节点\n book.removeChild(price);\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n\n public void delete2() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n Element price = (Element) document.getElementsByTagName(\"售价\").item(3);\n //通过子节点得到父节点再删除自己\n price.getParentNode().removeChild(price);\n //其他:通过子节点得到父节点的父节点的父节点 (删除父节点)...删除整个xml文档中的节点\n //price.getParentNode().getParentNode().getParentNode().removeChild(price.getParentNode().getParentNode());\n //删除指定节点的属性\n //price.removeAttribute(\"color\");\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n}\n```\n\n**更新**\n\n```java\npackage cn.xml;\n\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.xml.sax.SAXException;\n\npublic class Demo03_xml_update {\n public void update() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n Element price = (Element) document.getElementsByTagName(\"售价\").item(1);\n price.setTextContent(\"123.456元\");//更新标签中内容\n price.setAttribute(\"color\", \"eeed\");//更新标签属性\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n}\n```\n\n**遍历**\n\n```java\npackage cn.xml;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\npublic class Demo03_xml_read{\n //使用DOM方式对XML文档进行crud(增删改查)\n\n public void read1() throws Exception{\n\n //1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n //2.从DOM工厂获得DOM解析器 有了这个实例才可以解析\n DocumentBuilder builder = factory.newDocumentBuilder();\n //3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n //4.以后的处理都是对Document对象进行的\n\n //1.读取XML文档中<书名>....节点中的值\n NodeList list = document.getElementsByTagName(\"书名\");//按文档顺序返回包含在文档中且具有给定标记名称的所有 Element 的 NodeList。 返回节点集合\n Node node = list.item(1);//取第二个\"书名\"的节点\n String s = node.getTextContent();//得到节点的文本内容\n System.out.println(s);\n }\n\n //2.遍历整个XML文档中的所有节点(标签)\n public void read2() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n //得到根节点\n Node root = document.getElementsByTagName(\"书架\").item(0);//得到\"书架\"节点(根)\n //得到孩子并打印\n this.glist(root);\n }\n\n private void glist(Node node){\n //instanceof 在运行时指出对象是否是特定类的一个实例 返回布尔值\n if(node instanceof Element){//判断node是不是标签(元素) (因为xml的空格和换行符也能传进来)\n System.out.println(node.getNodeName());//打印节点名称\n NodeList list = node.getChildNodes();//得到孩子节点 返回孩子节点的集合\n for (int i = 0; i < list.getLength(); i++) {\n Node child = list.item(i);\n glist(child);\n }\n }\n }\n\n //3.得到\"书名\"标签中属性值\n public void read3() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n NodeList list = document.getElementsByTagName(\"书名\");\n //两种方法 第二个不推荐\n //把得到的\"书名\"节点强行转为标签类型\n Element bookname = (Element)list.item(0);\n String s = bookname.getAttribute(\"color\");//此方法可取得属性值\n/* \n Node bookname = list.item(0);\n String s= bookname.getAttributes().getNamedItem(\"color\").getNodeValue();\n*/\n\n System.out.println(s);\n }\n}\n```","source":"_posts/2017_jaxp.md","raw":"---\ntitle: jaxp解析xml文档实现增删改查 \n\ncomments: true \n\ntags: jaxp\n\ncategories: \n - DOM操作 \n - XML\n\ndescription: jaxp解析xml文档\n\ndate: 2017-02-05 #文章生成時間\n \n---\n\n\n## 这是被解析的示例xml\n\n```xml\n\n<书架>\n <书>\n <书名>java实战\n <作者>张三\n <售价>121元\n <售价>12元\n \n <书>\n <书名 color=\"yellow\" name=\"XXX\">c测试\n <作者>李四\n <售价 color=\"rrr\">54元\n <售价>12元\n\n```\n\n**增加**\n\n\n\n```java\npackage cn.xml;\n\nimport java.io.FileOutputStream;\nimport java.io.OutputStream;\n\nimport javax.xml.crypto.dsig.Transform;\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.transform.*;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\n\npublic class Demo03_xml_add {\n //4.向XML文档中添加节点 <售价>12元\n public void add() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n //在内存中的xml中创造一个节点\n Element price = document.createElement(\"售价\");//得到标签\n price.setTextContent(\"12元\");//添加标签中内容\n\n //先得到书节点 转换为标签\n Element book = (Element)document.getElementsByTagName(\"书\").item(0);\n //把创造的节点添加到xml文档中书标签下\n book.appendChild(price);\n //此时只更新了内存中的XML文档(document对象指向的) 所以要把document对象指向的内存中的xml文档更新到硬盘中的xml文档\n\n //更新后的写入XML文档\n //创建工厂实例\n TransformerFactory tf = TransformerFactory.newInstance();\n //通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)\n Transformer tr = tf.newTransformer();\n //DOMSource是Source的实现类 把Document类型封装为Source类型\n Source s = new DOMSource(document);\n //声明输出流对象 指向硬盘中的XML文件\n OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");\n //把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件\n Result r = new StreamResult(f);\n //transform方法(来源, 目的地) 把s写入r\n tr.transform(s, r);\n }\n\n //5.向XML文档中指定位置添加节点 <售价>999元\n public void add2() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n Element price = document.createElement(\"售价\");\n price.setTextContent(\"999元\");\n\n //得到参考节点 即下一个标签(Element是Node的一个子集 Element是Node的扩展)\n Element refNode = (Element) document.getElementsByTagName(\"售价\").item(1);\n\n //得到要插入的节点\n Element book = (Element) document.getElementsByTagName(\"书\").item(0); \n //插入book节点的指定位置 把price节点插入refNode节点之前\n book.insertBefore(price, refNode);//参数也可以是标签 \n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n\n //6.向XML文档中添加属性\n public void add3() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n //得到节点\n Element bookname = (Element) document.getElementsByTagName(\"书名\").item(0);\n bookname.setAttribute(\"name\", \"XXX\");\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n}\n```\n\n**删除**\n\n```java\npackage cn.xml;\n\nimport java.io.FileOutputStream;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\n\npublic class Demo03_xml_delete {\n public void delete() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n //得到要删除的节点\n Element price = (Element) document.getElementsByTagName(\"售价\").item(2);\n //得到要删除节点的父节点\n Element book = (Element) document.getElementsByTagName(\"书\").item(1);\n //通过父节点删除子节点\n book.removeChild(price);\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n\n public void delete2() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n Element price = (Element) document.getElementsByTagName(\"售价\").item(3);\n //通过子节点得到父节点再删除自己\n price.getParentNode().removeChild(price);\n //其他:通过子节点得到父节点的父节点的父节点 (删除父节点)...删除整个xml文档中的节点\n //price.getParentNode().getParentNode().getParentNode().removeChild(price.getParentNode().getParentNode());\n //删除指定节点的属性\n //price.removeAttribute(\"color\");\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n}\n```\n\n**更新**\n\n```java\npackage cn.xml;\n\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.transform.Transformer;\nimport javax.xml.transform.TransformerFactory;\nimport javax.xml.transform.dom.DOMSource;\nimport javax.xml.transform.stream.StreamResult;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.xml.sax.SAXException;\n\npublic class Demo03_xml_update {\n public void update() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n Element price = (Element) document.getElementsByTagName(\"售价\").item(1);\n price.setTextContent(\"123.456元\");//更新标签中内容\n price.setAttribute(\"color\", \"eeed\");//更新标签属性\n\n TransformerFactory tf = TransformerFactory.newInstance();\n Transformer tr = tf.newTransformer();\n tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));\n }\n}\n```\n\n**遍历**\n\n```java\npackage cn.xml;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\npublic class Demo03_xml_read{\n //使用DOM方式对XML文档进行crud(增删改查)\n\n public void read1() throws Exception{\n\n //1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n //2.从DOM工厂获得DOM解析器 有了这个实例才可以解析\n DocumentBuilder builder = factory.newDocumentBuilder();\n //3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n //4.以后的处理都是对Document对象进行的\n\n //1.读取XML文档中<书名>....节点中的值\n NodeList list = document.getElementsByTagName(\"书名\");//按文档顺序返回包含在文档中且具有给定标记名称的所有 Element 的 NodeList。 返回节点集合\n Node node = list.item(1);//取第二个\"书名\"的节点\n String s = node.getTextContent();//得到节点的文本内容\n System.out.println(s);\n }\n\n //2.遍历整个XML文档中的所有节点(标签)\n public void read2() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n //得到根节点\n Node root = document.getElementsByTagName(\"书架\").item(0);//得到\"书架\"节点(根)\n //得到孩子并打印\n this.glist(root);\n }\n\n private void glist(Node node){\n //instanceof 在运行时指出对象是否是特定类的一个实例 返回布尔值\n if(node instanceof Element){//判断node是不是标签(元素) (因为xml的空格和换行符也能传进来)\n System.out.println(node.getNodeName());//打印节点名称\n NodeList list = node.getChildNodes();//得到孩子节点 返回孩子节点的集合\n for (int i = 0; i < list.getLength(); i++) {\n Node child = list.item(i);\n glist(child);\n }\n }\n }\n\n //3.得到\"书名\"标签中属性值\n public void read3() throws Exception{\n DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();\n DocumentBuilder builder = factory.newDocumentBuilder();\n Document document = builder.parse(\"src/cn/xml/book.xml\");\n\n NodeList list = document.getElementsByTagName(\"书名\");\n //两种方法 第二个不推荐\n //把得到的\"书名\"节点强行转为标签类型\n Element bookname = (Element)list.item(0);\n String s = bookname.getAttribute(\"color\");//此方法可取得属性值\n/* \n Node bookname = list.item(0);\n String s= bookname.getAttributes().getNamedItem(\"color\").getNodeValue();\n*/\n\n System.out.println(s);\n }\n}\n```","slug":"2017_jaxp","published":1,"updated":"2018-01-13T02:29:22.244Z","layout":"post","photos":[],"link":"","_id":"ckm3inved008924ujdnhxq2r1","content":"

这是被解析的示例xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<书架>
<>
<书名>java实战</书名>
<作者>张三</作者>
<售价>121元</售价>
<售价>12元</售价>
</>
<>
<书名 color=\"yellow\" name=\"XXX\">c测试</书名>
<作者>李四</作者>
<售价 color=\"rrr\">54元</售价>
<售价>12元</售价></>
</书架>
\n

增加

\n\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package cn.xml;

import java.io.FileOutputStream;
import java.io.OutputStream;

import javax.xml.crypto.dsig.Transform;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class Demo03_xml_add {
//4.向XML文档中添加节点 <售价>12元</售价>
public void add() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

//在内存中的xml中创造一个节点
Element price = document.createElement(\"售价\");//得到标签
price.setTextContent(\"12元\");//添加标签中内容

//先得到书节点 转换为标签
Element book = (Element)document.getElementsByTagName(\"书\").item(0);
//把创造的节点添加到xml文档中书标签下
book.appendChild(price);
//此时只更新了内存中的XML文档(document对象指向的) 所以要把document对象指向的内存中的xml文档更新到硬盘中的xml文档

//更新后的写入XML文档
//创建工厂实例
TransformerFactory tf = TransformerFactory.newInstance();
//通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)
Transformer tr = tf.newTransformer();
//DOMSource是Source的实现类 把Document类型封装为Source类型
Source s = new DOMSource(document);
//声明输出流对象 指向硬盘中的XML文件
OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");
//把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件
Result r = new StreamResult(f);
//transform方法(来源, 目的地) 把s写入r
tr.transform(s, r);
}

//5.向XML文档中指定位置添加节点 <售价>999元</售价>
public void add2() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

Element price = document.createElement(\"售价\");
price.setTextContent(\"999元\");

//得到参考节点 即下一个标签(Element是Node的一个子集 Element是Node的扩展)
Element refNode = (Element) document.getElementsByTagName(\"售价\").item(1);

//得到要插入的节点
Element book = (Element) document.getElementsByTagName(\"书\").item(0);
//插入book节点的指定位置 把price节点插入refNode节点之前
book.insertBefore(price, refNode);//参数也可以是标签

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}

//6.向XML文档中添加属性
public void add3() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

//得到节点
Element bookname = (Element) document.getElementsByTagName(\"书名\").item(0);
bookname.setAttribute(\"name\", \"XXX\");

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}
}
\n

删除

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package cn.xml;

import java.io.FileOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class Demo03_xml_delete {
public void delete() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

//得到要删除的节点
Element price = (Element) document.getElementsByTagName(\"售价\").item(2);
//得到要删除节点的父节点
Element book = (Element) document.getElementsByTagName(\"书\").item(1);
//通过父节点删除子节点
book.removeChild(price);

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}

public void delete2() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

Element price = (Element) document.getElementsByTagName(\"售价\").item(3);
//通过子节点得到父节点再删除自己
price.getParentNode().removeChild(price);
//其他:通过子节点得到父节点的父节点的父节点 (删除父节点)...删除整个xml文档中的节点
//price.getParentNode().getParentNode().getParentNode().removeChild(price.getParentNode().getParentNode());
//删除指定节点的属性
//price.removeAttribute(\"color\");

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}
}
\n

更新

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package cn.xml;

import java.io.FileOutputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

public class Demo03_xml_update {
public void update() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

Element price = (Element) document.getElementsByTagName(\"售价\").item(1);
price.setTextContent(\"123.456元\");//更新标签中内容
price.setAttribute(\"color\", \"eeed\");//更新标签属性

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}
}
\n

遍历

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package cn.xml;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Demo03_xml_read{
//使用DOM方式对XML文档进行crud(增删改查)

public void read1() throws Exception{

//1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//2.从DOM工厂获得DOM解析器 有了这个实例才可以解析
DocumentBuilder builder = factory.newDocumentBuilder();
//3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象
Document document = builder.parse(\"src/cn/xml/book.xml\");
//4.以后的处理都是对Document对象进行的

//1.读取XML文档中<书名>....</书名>节点中的值
NodeList list = document.getElementsByTagName(\"书名\");//按文档顺序返回包含在文档中且具有给定标记名称的所有 Element 的 NodeList。 返回节点集合
Node node = list.item(1);//取第二个\"书名\"的节点
String s = node.getTextContent();//得到节点的文本内容
System.out.println(s);
}

//2.遍历整个XML文档中的所有节点(标签)
public void read2() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");
//得到根节点
Node root = document.getElementsByTagName(\"书架\").item(0);//得到\"书架\"节点(根)
//得到孩子并打印
this.glist(root);
}

private void glist(Node node){
//instanceof 在运行时指出对象是否是特定类的一个实例 返回布尔值
if(node instanceof Element){//判断node是不是标签(元素) (因为xml的空格和换行符也能传进来)
System.out.println(node.getNodeName());//打印节点名称
NodeList list = node.getChildNodes();//得到孩子节点 返回孩子节点的集合
for (int i = 0; i < list.getLength(); i++) {
Node child = list.item(i);
glist(child);
}
}
}

//3.得到\"书名\"标签中属性值
public void read3() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

NodeList list = document.getElementsByTagName(\"书名\");
//两种方法 第二个不推荐
//把得到的\"书名\"节点强行转为标签类型
Element bookname = (Element)list.item(0);
String s = bookname.getAttribute(\"color\");//此方法可取得属性值
/*
Node bookname = list.item(0);
String s= bookname.getAttributes().getNamedItem(\"color\").getNodeValue();
*/

System.out.println(s);
}
}
","site":{"data":{}},"excerpt":"

这是被解析的示例xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<书架>
<>
<书名>java实战</书名>
<作者>张三</作者>
<售价>121元</售价>
<售价>12元</售价>
</>
<>
<书名 color=\"yellow\" name=\"XXX\">c测试</书名>
<作者>李四</作者>
<售价 color=\"rrr\">54元</售价>
<售价>12元</售价></>
</书架>
\n

增加

","more":"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package cn.xml;

import java.io.FileOutputStream;
import java.io.OutputStream;

import javax.xml.crypto.dsig.Transform;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class Demo03_xml_add {
//4.向XML文档中添加节点 <售价>12元</售价>
public void add() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

//在内存中的xml中创造一个节点
Element price = document.createElement(\"售价\");//得到标签
price.setTextContent(\"12元\");//添加标签中内容

//先得到书节点 转换为标签
Element book = (Element)document.getElementsByTagName(\"书\").item(0);
//把创造的节点添加到xml文档中书标签下
book.appendChild(price);
//此时只更新了内存中的XML文档(document对象指向的) 所以要把document对象指向的内存中的xml文档更新到硬盘中的xml文档

//更新后的写入XML文档
//创建工厂实例
TransformerFactory tf = TransformerFactory.newInstance();
//通过工厂实例得到Transformer对象(transform方法可以转化来源到目的地)
Transformer tr = tf.newTransformer();
//DOMSource是Source的实现类 把Document类型封装为Source类型
Source s = new DOMSource(document);
//声明输出流对象 指向硬盘中的XML文件
OutputStream f= new FileOutputStream(\"src/cn/xml/book.xml\");
//把输出流对象通过流方法转化为Result对象 Result对象指向硬盘中的XML文件
Result r = new StreamResult(f);
//transform方法(来源, 目的地) 把s写入r
tr.transform(s, r);
}

//5.向XML文档中指定位置添加节点 <售价>999元</售价>
public void add2() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

Element price = document.createElement(\"售价\");
price.setTextContent(\"999元\");

//得到参考节点 即下一个标签(Element是Node的一个子集 Element是Node的扩展)
Element refNode = (Element) document.getElementsByTagName(\"售价\").item(1);

//得到要插入的节点
Element book = (Element) document.getElementsByTagName(\"书\").item(0);
//插入book节点的指定位置 把price节点插入refNode节点之前
book.insertBefore(price, refNode);//参数也可以是标签

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}

//6.向XML文档中添加属性
public void add3() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

//得到节点
Element bookname = (Element) document.getElementsByTagName(\"书名\").item(0);
bookname.setAttribute(\"name\", \"XXX\");

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}
}
\n

删除

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package cn.xml;

import java.io.FileOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class Demo03_xml_delete {
public void delete() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

//得到要删除的节点
Element price = (Element) document.getElementsByTagName(\"售价\").item(2);
//得到要删除节点的父节点
Element book = (Element) document.getElementsByTagName(\"书\").item(1);
//通过父节点删除子节点
book.removeChild(price);

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}

public void delete2() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

Element price = (Element) document.getElementsByTagName(\"售价\").item(3);
//通过子节点得到父节点再删除自己
price.getParentNode().removeChild(price);
//其他:通过子节点得到父节点的父节点的父节点 (删除父节点)...删除整个xml文档中的节点
//price.getParentNode().getParentNode().getParentNode().removeChild(price.getParentNode().getParentNode());
//删除指定节点的属性
//price.removeAttribute(\"color\");

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}
}
\n

更新

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package cn.xml;

import java.io.FileOutputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

public class Demo03_xml_update {
public void update() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

Element price = (Element) document.getElementsByTagName(\"售价\").item(1);
price.setTextContent(\"123.456元\");//更新标签中内容
price.setAttribute(\"color\", \"eeed\");//更新标签属性

TransformerFactory tf = TransformerFactory.newInstance();
Transformer tr = tf.newTransformer();
tr.transform(new DOMSource(document), new StreamResult(new FileOutputStream(\"src/cn/xml/book.xml\")));
}
}
\n

遍历

\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package cn.xml;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Demo03_xml_read{
//使用DOM方式对XML文档进行crud(增删改查)

public void read1() throws Exception{

//1.创建工厂(得到DOM解析器的工厂实例) ---这个工厂类是抽象类,so用其newInstance方法得到DOM的新实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//2.从DOM工厂获得DOM解析器 有了这个实例才可以解析
DocumentBuilder builder = factory.newDocumentBuilder();
//3.将给定URI的内容解析为一个XML文档,并且返回一个新的DOM Document对象
Document document = builder.parse(\"src/cn/xml/book.xml\");
//4.以后的处理都是对Document对象进行的

//1.读取XML文档中<书名>....</书名>节点中的值
NodeList list = document.getElementsByTagName(\"书名\");//按文档顺序返回包含在文档中且具有给定标记名称的所有 Element 的 NodeList。 返回节点集合
Node node = list.item(1);//取第二个\"书名\"的节点
String s = node.getTextContent();//得到节点的文本内容
System.out.println(s);
}

//2.遍历整个XML文档中的所有节点(标签)
public void read2() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");
//得到根节点
Node root = document.getElementsByTagName(\"书架\").item(0);//得到\"书架\"节点(根)
//得到孩子并打印
this.glist(root);
}

private void glist(Node node){
//instanceof 在运行时指出对象是否是特定类的一个实例 返回布尔值
if(node instanceof Element){//判断node是不是标签(元素) (因为xml的空格和换行符也能传进来)
System.out.println(node.getNodeName());//打印节点名称
NodeList list = node.getChildNodes();//得到孩子节点 返回孩子节点的集合
for (int i = 0; i < list.getLength(); i++) {
Node child = list.item(i);
glist(child);
}
}
}

//3.得到\"书名\"标签中属性值
public void read3() throws Exception{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(\"src/cn/xml/book.xml\");

NodeList list = document.getElementsByTagName(\"书名\");
//两种方法 第二个不推荐
//把得到的\"书名\"节点强行转为标签类型
Element bookname = (Element)list.item(0);
String s = bookname.getAttribute(\"color\");//此方法可取得属性值
/*
Node bookname = list.item(0);
String s= bookname.getAttributes().getNamedItem(\"color\").getNodeValue();
*/

System.out.println(s);
}
}
"},{"title":"重新学习JDBC之获取连接","comments":1,"description":null,"date":"2017-02-05T16:00:00.000Z","_content":"\n**JDBC是一种用于执行sql语句的javaAPI, 为多种关系数据库提供统一访问有java编写的类和接口组成**\n\njdbc其实就是一种规范(基准),根据其可以自己构建更高级的框架,如(hibernate等),\n编程人员须根据这个规范编写来操作数据库\n\n----------------\n\n\n\n## >**Driver** 提供了一个接口 数据库厂商需要自己的产品编写此接口的实现类 通过创建实现类的对象(注册)加载相应的数据库驱动\n\n## *1,原始的jdbc尝试(获取数据库连接)*\n\n>*连接需要某个厂商的数据库驱动包 这里连接mysql \n官网下载地址 https://dev.mysql.com/downloads/connector/j/ 里面的 mysql-connector-java-*.jar文件\n\n \n @Test\n public void testDriver() throws SQLException {\n //创建Driver实现类(这里是mysql的实现类) 同时注册驱动(见下面mysql源码参照1)\n Driver driver = new com.mysql.jdbc.Driver();\n \n //Properties类用于读取java的配置文件(.properties) 这里并没有读取文件\n String url = \"jdbc:mysql://127.0.0.1:3306/test\"; //数据库地址\n Properties properties = new Properties();\n properties.put(\"user\", \"root\"); //数据库用户名\n properties.put(\"password\", \"anyao112233\");//数据库密码\n \n //获取数据库连接 返回一个Connection的mysql的实现类\n Connection connection = driver.connect(url, properties);//见下面源码参照2\n \n System.out.println(connection);\n }\n\n>*mysql的源码参照1*\n\n\n package com.mysql.jdbc;\n //一旦声明了此类的对象就会先调用这里静态代码块中的代码 实现驱动的注册(registerDriver)\n public class Driver extends NonRegisteringDriver implements java.sql.Driver {\n public Driver() throws SQLException {\n }\n \n static {\n try {\n //其实在注册的时候就已经实例化过一次driver对象 不需要自己new或newInstance\n //但是,由于此实例化为匿名,so只能自己再实例一遍,以获得实例化的对象\n DriverManager.registerDriver(new Driver());\n } catch (SQLException var1) {\n throw new RuntimeException(\"Can\\'t register driver!\");\n }\n }\n }\n\t\n\t\n> *mysql的源码参照2*\n\n \n package com.mysql.jdbc;\n public class NonRegisteringDriver implements Driver {\n public Connection connect(String url, Properties info) throws SQLException {\n //检测url是否为空(现在URL是jdbc:mysql://127.0.0.1:3306/test)\n if(url != null) {\n //检测url是否以后者开头---否\n if(StringUtils.startsWithIgnoreCase(url, \"jdbc:mysql:loadbalance://\")) {\n return this.connectLoadBalanced(url, info);\n }\n //检测url是否以后者开头---否\n if(StringUtils.startsWithIgnoreCase(url, \"jdbc:mysql:replication://\")) {\n return this.connectReplicationConnection(url, info);\n }\n }\n Properties props = null;\n //获取url中信息存放到props对象中(HOST,user,password,DBNAME)\n if((props = this.parseURL(url, info)) == null) {\n return null;\n } else {\n try {\n //以props中的信息创建数据库连接 返回代表连接的对象\n com.mysql.jdbc.Connection ex = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);\n //返回连接对象\n return ex;\n //下面是各种异常\n } catch (SQLException var6) {\n throw var6;\n } catch (Exception var7) {\n SQLException sqlEx = SQLError.createSQLException(Messages.getString(\"NonRegisteringDriver.17\") + var7.toString() + Messages.getString(\"NonRegisteringDriver.18\"), \"08001\");\n sqlEx.initCause(var7);\n throw sqlEx;\n }\n }\n }\n }\n \n \n*各位看官, 自己可以debug调试看看流程 其实也不难看懂,人家的代码都写得很清楚了*\n----\n\n## **2,升级版本**\n\n配置文件内容\n![](http://www.zj2626.github.io/wp-content/uploads/2017/02/aaa.png)(如图,地址要加字符编码,防止中文乱码)\n\n \n @Test\n public void test2() {\n getConnection();\n }\n \n //把获取数据库连接封装为一个类,把数据库的信息存放到配置文件中\n //原因:一般程序部署以后就不会再更改代码,如果数据有变,就可以只是更改配置文件,\n // 而要是这些信息放在代码中,则需要重新编译运行才能生效\n private Connection getConnection() {\n String dirverClass;\n String url;\n String user;\n String password;\n \n //读取文件\n //getClassLoader返回类的类加载器 \n //getResourceAsStream把指定目录的内容返回到一个输入流中\n InputStream inputStream = getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\");\n Properties properties = new Properties();\n try {\n //读取输入流中的内容到properties对象中\n properties.load(inputStream);\n } catch (IOException e) {\n e.printStackTrace();\n }\n //按照键值对的形式读取对象中该键所对应的值 获取到数据库信息\n dirverClass = properties.getProperty(\"driver\");\n url = properties.getProperty(\"jdbc_url\");\n user = properties.getProperty(\"user\");\n password = properties.getProperty(\"password\");\n \n Properties info = new Properties();\n info.put(\"user\", user);\n info.put(\"password\", password);\n \n Driver driver;\n Connection connection = null;\n try {\n //forName用于使JVM加载指定的类(全类名,写在配置文件中),即动态加载和创建Class 对象\n //所有类都是Class类的对象\n //通过刚才加载的Class对象(也就是你需要的)来实例化加载了的所需类的对象,效果同new一个对象\n driver = (Driver) Class.forName(dirverClass).newInstance();\n //获取连接\n connection = driver.connect(url, info);\n System.out.println(connection);\n } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {\n e.printStackTrace();\n } finally {\n if (connection != null) {\n try {\n connection.close();//关闭连接\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n \n return null;\n }\n \n---\n\n## **3,再次升级版本**\n\n此时发现,通过Driver类直接控制数据库连接太麻烦,于是有了DriverManager\n>DriverManager用来管理数据库中所有的驱动程序,把driver的创建,获取连接等过程交由DriverManager管理\n\n \n @Test\n public void test() {\n Properties properties = new Properties();\n try {\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n \n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n Connection connection = null;\n try {\n //加载数据库驱动 可以多个驱动程序注册(在注册时候就已经实例化了一个对象,不需要自己新建driver对象)\n Class.forName(dirverClass);\n //获取数据库连接\n //获取的时候会扫描所有的注册的驱动 找到合适的驱动然后获取连接\n connection = DriverManager.getConnection(url, user, password);\n System.out.println(connection);\n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n","source":"_posts/2017_jdbc_getConnection.md","raw":"---\ntitle: 重新学习JDBC之获取连接\n\ncomments: true \n\ntags: \n - JDBC\n\ncategories: \n - JDBC\n\ndescription: \n\ndate: 2017-02-06\n \n---\n\n**JDBC是一种用于执行sql语句的javaAPI, 为多种关系数据库提供统一访问有java编写的类和接口组成**\n\njdbc其实就是一种规范(基准),根据其可以自己构建更高级的框架,如(hibernate等),\n编程人员须根据这个规范编写来操作数据库\n\n----------------\n\n\n\n## >**Driver** 提供了一个接口 数据库厂商需要自己的产品编写此接口的实现类 通过创建实现类的对象(注册)加载相应的数据库驱动\n\n## *1,原始的jdbc尝试(获取数据库连接)*\n\n>*连接需要某个厂商的数据库驱动包 这里连接mysql \n官网下载地址 https://dev.mysql.com/downloads/connector/j/ 里面的 mysql-connector-java-*.jar文件\n\n \n @Test\n public void testDriver() throws SQLException {\n //创建Driver实现类(这里是mysql的实现类) 同时注册驱动(见下面mysql源码参照1)\n Driver driver = new com.mysql.jdbc.Driver();\n \n //Properties类用于读取java的配置文件(.properties) 这里并没有读取文件\n String url = \"jdbc:mysql://127.0.0.1:3306/test\"; //数据库地址\n Properties properties = new Properties();\n properties.put(\"user\", \"root\"); //数据库用户名\n properties.put(\"password\", \"anyao112233\");//数据库密码\n \n //获取数据库连接 返回一个Connection的mysql的实现类\n Connection connection = driver.connect(url, properties);//见下面源码参照2\n \n System.out.println(connection);\n }\n\n>*mysql的源码参照1*\n\n\n package com.mysql.jdbc;\n //一旦声明了此类的对象就会先调用这里静态代码块中的代码 实现驱动的注册(registerDriver)\n public class Driver extends NonRegisteringDriver implements java.sql.Driver {\n public Driver() throws SQLException {\n }\n \n static {\n try {\n //其实在注册的时候就已经实例化过一次driver对象 不需要自己new或newInstance\n //但是,由于此实例化为匿名,so只能自己再实例一遍,以获得实例化的对象\n DriverManager.registerDriver(new Driver());\n } catch (SQLException var1) {\n throw new RuntimeException(\"Can\\'t register driver!\");\n }\n }\n }\n\t\n\t\n> *mysql的源码参照2*\n\n \n package com.mysql.jdbc;\n public class NonRegisteringDriver implements Driver {\n public Connection connect(String url, Properties info) throws SQLException {\n //检测url是否为空(现在URL是jdbc:mysql://127.0.0.1:3306/test)\n if(url != null) {\n //检测url是否以后者开头---否\n if(StringUtils.startsWithIgnoreCase(url, \"jdbc:mysql:loadbalance://\")) {\n return this.connectLoadBalanced(url, info);\n }\n //检测url是否以后者开头---否\n if(StringUtils.startsWithIgnoreCase(url, \"jdbc:mysql:replication://\")) {\n return this.connectReplicationConnection(url, info);\n }\n }\n Properties props = null;\n //获取url中信息存放到props对象中(HOST,user,password,DBNAME)\n if((props = this.parseURL(url, info)) == null) {\n return null;\n } else {\n try {\n //以props中的信息创建数据库连接 返回代表连接的对象\n com.mysql.jdbc.Connection ex = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);\n //返回连接对象\n return ex;\n //下面是各种异常\n } catch (SQLException var6) {\n throw var6;\n } catch (Exception var7) {\n SQLException sqlEx = SQLError.createSQLException(Messages.getString(\"NonRegisteringDriver.17\") + var7.toString() + Messages.getString(\"NonRegisteringDriver.18\"), \"08001\");\n sqlEx.initCause(var7);\n throw sqlEx;\n }\n }\n }\n }\n \n \n*各位看官, 自己可以debug调试看看流程 其实也不难看懂,人家的代码都写得很清楚了*\n----\n\n## **2,升级版本**\n\n配置文件内容\n![](http://www.zj2626.github.io/wp-content/uploads/2017/02/aaa.png)(如图,地址要加字符编码,防止中文乱码)\n\n \n @Test\n public void test2() {\n getConnection();\n }\n \n //把获取数据库连接封装为一个类,把数据库的信息存放到配置文件中\n //原因:一般程序部署以后就不会再更改代码,如果数据有变,就可以只是更改配置文件,\n // 而要是这些信息放在代码中,则需要重新编译运行才能生效\n private Connection getConnection() {\n String dirverClass;\n String url;\n String user;\n String password;\n \n //读取文件\n //getClassLoader返回类的类加载器 \n //getResourceAsStream把指定目录的内容返回到一个输入流中\n InputStream inputStream = getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\");\n Properties properties = new Properties();\n try {\n //读取输入流中的内容到properties对象中\n properties.load(inputStream);\n } catch (IOException e) {\n e.printStackTrace();\n }\n //按照键值对的形式读取对象中该键所对应的值 获取到数据库信息\n dirverClass = properties.getProperty(\"driver\");\n url = properties.getProperty(\"jdbc_url\");\n user = properties.getProperty(\"user\");\n password = properties.getProperty(\"password\");\n \n Properties info = new Properties();\n info.put(\"user\", user);\n info.put(\"password\", password);\n \n Driver driver;\n Connection connection = null;\n try {\n //forName用于使JVM加载指定的类(全类名,写在配置文件中),即动态加载和创建Class 对象\n //所有类都是Class类的对象\n //通过刚才加载的Class对象(也就是你需要的)来实例化加载了的所需类的对象,效果同new一个对象\n driver = (Driver) Class.forName(dirverClass).newInstance();\n //获取连接\n connection = driver.connect(url, info);\n System.out.println(connection);\n } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {\n e.printStackTrace();\n } finally {\n if (connection != null) {\n try {\n connection.close();//关闭连接\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n \n return null;\n }\n \n---\n\n## **3,再次升级版本**\n\n此时发现,通过Driver类直接控制数据库连接太麻烦,于是有了DriverManager\n>DriverManager用来管理数据库中所有的驱动程序,把driver的创建,获取连接等过程交由DriverManager管理\n\n \n @Test\n public void test() {\n Properties properties = new Properties();\n try {\n properties.load(this.getClass().getClassLoader().getResourceAsStream(\"com/jdbc/jdbc.properties\"));\n } catch (IOException e) {\n e.printStackTrace();\n }\n \n String dirverClass = properties.getProperty(\"driver\");\n String url = properties.getProperty(\"jdbc_url\");\n String user = properties.getProperty(\"user\");\n String password = properties.getProperty(\"password\");\n Connection connection = null;\n try {\n //加载数据库驱动 可以多个驱动程序注册(在注册时候就已经实例化了一个对象,不需要自己新建driver对象)\n Class.forName(dirverClass);\n //获取数据库连接\n //获取的时候会扫描所有的注册的驱动 找到合适的驱动然后获取连接\n connection = DriverManager.getConnection(url, user, password);\n System.out.println(connection);\n } catch (SQLException | ClassNotFoundException e) {\n e.printStackTrace();\n }\n }\n","slug":"2017_jdbc_getConnection","published":1,"updated":"2021-03-10T13:50:15.390Z","layout":"post","photos":[],"link":"","_id":"ckm3invef008c24uj72p29fg7","content":"

JDBC是一种用于执行sql语句的javaAPI, 为多种关系数据库提供统一访问有java编写的类和接口组成

\n

jdbc其实就是一种规范(基准),根据其可以自己构建更高级的框架,如(hibernate等),
编程人员须根据这个规范编写来操作数据库

\n
\n\n

>Driver 提供了一个接口 数据库厂商需要自己的产品编写此接口的实现类 通过创建实现类的对象(注册)加载相应的数据库驱动

1,原始的jdbc尝试(获取数据库连接)

\n

连接需要某个厂商的数据库驱动包 这里连接mysql
官网下载地址 https://dev.mysql.com/downloads/connector/j/ 里面的 mysql-connector-java-
.jar文件

\n
\n
 @Test\npublic void testDriver() throws SQLException {\n    //创建Driver实现类(这里是mysql的实现类) 同时注册驱动(见下面mysql源码参照1)\n    Driver driver = new com.mysql.jdbc.Driver();\n\n    //Properties类用于读取java的配置文件(.properties) 这里并没有读取文件\n    String url = "jdbc:mysql://127.0.0.1:3306/test"; //数据库地址\n    Properties properties = new Properties();\n    properties.put("user", "root"); //数据库用户名\n    properties.put("password", "anyao112233");//数据库密码\n\n    //获取数据库连接 返回一个Connection的mysql的实现类\n    Connection connection = driver.connect(url, properties);//见下面源码参照2\n\n    System.out.println(connection);\n}\n
\n

mysql的源码参照1

\n
\n
package com.mysql.jdbc;\n//一旦声明了此类的对象就会先调用这里静态代码块中的代码 实现驱动的注册(registerDriver)\npublic class Driver extends NonRegisteringDriver implements java.sql.Driver {\n        public Driver() throws SQLException {\n        }\n\n        static {\n                try {\n                        //其实在注册的时候就已经实例化过一次driver对象 不需要自己new或newInstance\n                        //但是,由于此实例化为匿名,so只能自己再实例一遍,以获得实例化的对象\n                        DriverManager.registerDriver(new Driver());\n                } catch (SQLException var1) {\n                        throw new RuntimeException("Can\\'t register driver!");\n                }\n        }\n}\n
\n

mysql的源码参照2

\n
\n
package com.mysql.jdbc;\npublic class NonRegisteringDriver implements Driver {\n    public Connection connect(String url, Properties info) throws SQLException {\n                    //检测url是否为空(现在URL是jdbc:mysql://127.0.0.1:3306/test)\n                    if(url != null) {\n                            //检测url是否以后者开头---否\n                            if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://")) {\n                                    return this.connectLoadBalanced(url, info);\n                            }\n                            //检测url是否以后者开头---否\n                            if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://")) {\n                                    return this.connectReplicationConnection(url, info);\n                            }\n                    }\n                    Properties props = null;\n                    //获取url中信息存放到props对象中(HOST,user,password,DBNAME)\n                    if((props = this.parseURL(url, info)) == null) {\n                            return null;\n                    } else {\n                            try {\n                                    //以props中的信息创建数据库连接 返回代表连接的对象\n                                    com.mysql.jdbc.Connection ex = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);\n                                    //返回连接对象\n                                    return ex;\n                                    //下面是各种异常\n                            } catch (SQLException var6) {\n                                    throw var6;\n                            } catch (Exception var7) {\n                                    SQLException sqlEx = SQLError.createSQLException(Messages.getString("NonRegisteringDriver.17") + var7.toString() + Messages.getString("NonRegisteringDriver.18"), "08001");\n                                    sqlEx.initCause(var7);\n                                    throw sqlEx;\n                            }\n                    }\n            }\n}\n

各位看官, 自己可以debug调试看看流程 其实也不难看懂,人家的代码都写得很清楚了

2,升级版本

配置文件内容
(如图,地址要加字符编码,防止中文乱码)

\n
@Test\n    public void test2() {\n        getConnection();\n    }\n\n    //把获取数据库连接封装为一个类,把数据库的信息存放到配置文件中\n    //原因:一般程序部署以后就不会再更改代码,如果数据有变,就可以只是更改配置文件,\n    //    而要是这些信息放在代码中,则需要重新编译运行才能生效\n    private Connection getConnection() {\n        String dirverClass;\n        String url;\n        String user;\n        String password;\n\n        //读取文件\n        //getClassLoader返回类的类加载器 \n        //getResourceAsStream把指定目录的内容返回到一个输入流中\n        InputStream inputStream = getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties");\n        Properties properties = new Properties();\n        try {\n            //读取输入流中的内容到properties对象中\n            properties.load(inputStream);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        //按照键值对的形式读取对象中该键所对应的值 获取到数据库信息\n        dirverClass = properties.getProperty("driver");\n        url = properties.getProperty("jdbc_url");\n        user = properties.getProperty("user");\n        password = properties.getProperty("password");\n\n        Properties info = new Properties();\n        info.put("user", user);\n        info.put("password", password);\n\n        Driver driver;\n        Connection connection = null;\n        try {\n            //forName用于使JVM加载指定的类(全类名,写在配置文件中),即动态加载和创建Class 对象\n            //所有类都是Class类的对象\n            //通过刚才加载的Class对象(也就是你需要的)来实例化加载了的所需类的对象,效果同new一个对象\n            driver = (Driver) Class.forName(dirverClass).newInstance();\n            //获取连接\n            connection = driver.connect(url, info);\n            System.out.println(connection);\n        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {\n            e.printStackTrace();\n        } finally {\n            if (connection != null) {\n                try {\n                    connection.close();//关闭连接\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return null;\n    }\n

\n

3,再次升级版本

此时发现,通过Driver类直接控制数据库连接太麻烦,于是有了DriverManager

\n
\n

DriverManager用来管理数据库中所有的驱动程序,把driver的创建,获取连接等过程交由DriverManager管理

\n
\n
@Test\n    public void test() {\n        Properties properties = new Properties();\n        try {\n            properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n\n        String dirverClass = properties.getProperty("driver");\n        String url = properties.getProperty("jdbc_url");\n        String user = properties.getProperty("user");\n        String password = properties.getProperty("password");\n        Connection connection = null;\n        try {\n            //加载数据库驱动 可以多个驱动程序注册(在注册时候就已经实例化了一个对象,不需要自己新建driver对象)\n            Class.forName(dirverClass);\n            //获取数据库连接\n            //获取的时候会扫描所有的注册的驱动 找到合适的驱动然后获取连接\n            connection = DriverManager.getConnection(url, user, password);\n            System.out.println(connection);\n        } catch (SQLException | ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n
","site":{"data":{}},"excerpt":"

JDBC是一种用于执行sql语句的javaAPI, 为多种关系数据库提供统一访问有java编写的类和接口组成

\n

jdbc其实就是一种规范(基准),根据其可以自己构建更高级的框架,如(hibernate等),
编程人员须根据这个规范编写来操作数据库

\n
","more":"

>Driver 提供了一个接口 数据库厂商需要自己的产品编写此接口的实现类 通过创建实现类的对象(注册)加载相应的数据库驱动

1,原始的jdbc尝试(获取数据库连接)

\n

连接需要某个厂商的数据库驱动包 这里连接mysql
官网下载地址 https://dev.mysql.com/downloads/connector/j/ 里面的 mysql-connector-java-
.jar文件

\n
\n
 @Test\npublic void testDriver() throws SQLException {\n    //创建Driver实现类(这里是mysql的实现类) 同时注册驱动(见下面mysql源码参照1)\n    Driver driver = new com.mysql.jdbc.Driver();\n\n    //Properties类用于读取java的配置文件(.properties) 这里并没有读取文件\n    String url = "jdbc:mysql://127.0.0.1:3306/test"; //数据库地址\n    Properties properties = new Properties();\n    properties.put("user", "root"); //数据库用户名\n    properties.put("password", "anyao112233");//数据库密码\n\n    //获取数据库连接 返回一个Connection的mysql的实现类\n    Connection connection = driver.connect(url, properties);//见下面源码参照2\n\n    System.out.println(connection);\n}\n
\n

mysql的源码参照1

\n
\n
package com.mysql.jdbc;\n//一旦声明了此类的对象就会先调用这里静态代码块中的代码 实现驱动的注册(registerDriver)\npublic class Driver extends NonRegisteringDriver implements java.sql.Driver {\n        public Driver() throws SQLException {\n        }\n\n        static {\n                try {\n                        //其实在注册的时候就已经实例化过一次driver对象 不需要自己new或newInstance\n                        //但是,由于此实例化为匿名,so只能自己再实例一遍,以获得实例化的对象\n                        DriverManager.registerDriver(new Driver());\n                } catch (SQLException var1) {\n                        throw new RuntimeException("Can\\'t register driver!");\n                }\n        }\n}\n
\n

mysql的源码参照2

\n
\n
package com.mysql.jdbc;\npublic class NonRegisteringDriver implements Driver {\n    public Connection connect(String url, Properties info) throws SQLException {\n                    //检测url是否为空(现在URL是jdbc:mysql://127.0.0.1:3306/test)\n                    if(url != null) {\n                            //检测url是否以后者开头---否\n                            if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://")) {\n                                    return this.connectLoadBalanced(url, info);\n                            }\n                            //检测url是否以后者开头---否\n                            if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://")) {\n                                    return this.connectReplicationConnection(url, info);\n                            }\n                    }\n                    Properties props = null;\n                    //获取url中信息存放到props对象中(HOST,user,password,DBNAME)\n                    if((props = this.parseURL(url, info)) == null) {\n                            return null;\n                    } else {\n                            try {\n                                    //以props中的信息创建数据库连接 返回代表连接的对象\n                                    com.mysql.jdbc.Connection ex = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);\n                                    //返回连接对象\n                                    return ex;\n                                    //下面是各种异常\n                            } catch (SQLException var6) {\n                                    throw var6;\n                            } catch (Exception var7) {\n                                    SQLException sqlEx = SQLError.createSQLException(Messages.getString("NonRegisteringDriver.17") + var7.toString() + Messages.getString("NonRegisteringDriver.18"), "08001");\n                                    sqlEx.initCause(var7);\n                                    throw sqlEx;\n                            }\n                    }\n            }\n}\n

各位看官, 自己可以debug调试看看流程 其实也不难看懂,人家的代码都写得很清楚了

2,升级版本

配置文件内容
(如图,地址要加字符编码,防止中文乱码)

\n
@Test\n    public void test2() {\n        getConnection();\n    }\n\n    //把获取数据库连接封装为一个类,把数据库的信息存放到配置文件中\n    //原因:一般程序部署以后就不会再更改代码,如果数据有变,就可以只是更改配置文件,\n    //    而要是这些信息放在代码中,则需要重新编译运行才能生效\n    private Connection getConnection() {\n        String dirverClass;\n        String url;\n        String user;\n        String password;\n\n        //读取文件\n        //getClassLoader返回类的类加载器 \n        //getResourceAsStream把指定目录的内容返回到一个输入流中\n        InputStream inputStream = getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties");\n        Properties properties = new Properties();\n        try {\n            //读取输入流中的内容到properties对象中\n            properties.load(inputStream);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        //按照键值对的形式读取对象中该键所对应的值 获取到数据库信息\n        dirverClass = properties.getProperty("driver");\n        url = properties.getProperty("jdbc_url");\n        user = properties.getProperty("user");\n        password = properties.getProperty("password");\n\n        Properties info = new Properties();\n        info.put("user", user);\n        info.put("password", password);\n\n        Driver driver;\n        Connection connection = null;\n        try {\n            //forName用于使JVM加载指定的类(全类名,写在配置文件中),即动态加载和创建Class 对象\n            //所有类都是Class类的对象\n            //通过刚才加载的Class对象(也就是你需要的)来实例化加载了的所需类的对象,效果同new一个对象\n            driver = (Driver) Class.forName(dirverClass).newInstance();\n            //获取连接\n            connection = driver.connect(url, info);\n            System.out.println(connection);\n        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {\n            e.printStackTrace();\n        } finally {\n            if (connection != null) {\n                try {\n                    connection.close();//关闭连接\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n\n        return null;\n    }\n

\n

3,再次升级版本

此时发现,通过Driver类直接控制数据库连接太麻烦,于是有了DriverManager

\n
\n

DriverManager用来管理数据库中所有的驱动程序,把driver的创建,获取连接等过程交由DriverManager管理

\n
\n
@Test\n    public void test() {\n        Properties properties = new Properties();\n        try {\n            properties.load(this.getClass().getClassLoader().getResourceAsStream("com/jdbc/jdbc.properties"));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n\n        String dirverClass = properties.getProperty("driver");\n        String url = properties.getProperty("jdbc_url");\n        String user = properties.getProperty("user");\n        String password = properties.getProperty("password");\n        Connection connection = null;\n        try {\n            //加载数据库驱动 可以多个驱动程序注册(在注册时候就已经实例化了一个对象,不需要自己新建driver对象)\n            Class.forName(dirverClass);\n            //获取数据库连接\n            //获取的时候会扫描所有的注册的驱动 找到合适的驱动然后获取连接\n            connection = DriverManager.getConnection(url, user, password);\n            System.out.println(connection);\n        } catch (SQLException | ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n
"},{"title":"dbcp,c3p0,druid简单实例(包含配置介绍)","comments":1,"description":null,"date":"2017-02-12T16:00:00.000Z","_content":"\n# **三种连接池的配置:dbcp、c3p0、druid**\n\n\n\n# dbcp\n ## 1.硬编码方式\n \n @Test\n public void testDBCPCode() {\n //BasicDataSource实现接口DataSource DBCP连接池核心类\n BasicDataSource dataSouce = new BasicDataSource();\n dataSouce.setDriverClassName(\"com.mysql.jdbc.Driver\"); //驱动\n dataSouce.setUrl(\"jdbc:mysql://127.0.0.1:3306/test\"); //数据库连接字符串\n dataSouce.setUsername(\"root\"); //数据库用户名\n dataSouce.setPassword(\"anyao112233\"); //数据库密码\n dataSouce.setInitialSize(5); //设置初始化连接数\n dataSouce.setMaxTotal(5); //设置最大连接数\n dataSouce.setMaxWaitMillis(10000); //设置申请连接最大等待时间\n dataSouce.setRemoveAbandonedTimeout(60); //设置空闲连接时长 超过就回收没用的连接\n Connection connection = null;\n try {\n connection = dataSouce.getConnection();\n // connection.prepareStatement(\"update student set f_name = 'fuk' where f_id = 10\").executeUpdate();\n System.out.println(connection.getClass() + \"\\n\" + connection.getMetaData() + \"\\n\");\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n // 关闭\n if (connection != null) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n\n ## 2.配置方式\n *文件jdbc.properties*\n \n #驱动\n driverClassName=com.mysql.jdbc.Driver\n #数据库连接地址\n url=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n #用户名\n username=root\n #密码\n password=anyao112233\n #初始化连接\n initialSize=10\n #连接池的最大数据库连接数。设为0表示无限制\n maxTotal=50\n ##最小空闲连接\n minIdle=10\n #最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n #空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\n maxIdle=50\n #超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\n removeAbandonedTimeout=60\n #超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\n removeAbandoned=true\n #最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\n maxWaitMillis=60000\n #在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\n timeBetweenEvictionRunsMillis=60000\n #连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\n minEvictableIdleTimeMillis=300000\n \n\n\n @Test\n public void testDBCPXML() throws Exception {\n Connection connection = null;\n try {\n Properties prop = new Properties();\n InputStream inStream = this.getClass().getClassLoader()\n .getResourceAsStream(\"jdbc.properties\");\n prop.load(inStream);\n System.out.println(prop);\n // 根据prop配置,直接创建数据源对象(BasicDataSourceFactory工厂)\n DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);\n // 获取连接\n connection = dataSouce.getConnection();\n // connection.prepareStatement(\"delete from student where f_id=1\").executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n // 关闭\n if (connection != null) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n \n ---\n \n # ** C3P0**\n \n ## 1.硬编码方式\n \n @Test\n public void testC3P0Code() throws Exception {\n // 创建连接池核心工具类\n ComboPooledDataSource dataSource = new ComboPooledDataSource();\n \n dataSource.setJdbcUrl(\"jdbc:mysql://localhost:3306/test\");\n dataSource.setDriverClass(\"com.mysql.jdbc.Driver\");\n dataSource.setUser(\"root\");\n dataSource.setPassword(\"anyao112233\");\n dataSource.setInitialPoolSize(3);//连接池初始化时创建的连接数\n dataSource.setMaxPoolSize(6);//连接池中拥有的最大连接数\n dataSource.setMaxIdleTime(1000);\n Connection connection = null;\n try {\n connection = dataSource.getConnection();\n connection.prepareStatement(\"delete from student where f_name like '%name%'\").executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n if (connection != null) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n \n ## 2.配置方式\n \n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n\t\n\t\n\t\t@Test\n\t\tpublic void testC3P0XML() throws Exception {\n\t\t\t\t// 创建c3p0连接池核心工具类\n\t\t\t\t// 自动加载src下c3p0的配置文件【c3p0-config.xml】\n\t\t\t\t//如果要使用default-config无需传参数,\n\t\t\t\t//如果要使用named-config里面配置初始化数据源,则只要使用一个带参数的ComboPooledDataSource构造器就可以了\n\t\t\t\tDataSource dataSource = new ComboPooledDataSource(\"myApp\");\n\t\t\t\tConnection connection = null;\n\t\t\t\ttry {\n\t\t\t\t\t\tconnection = dataSource.getConnection();\n\t\t\t\t\t\tconnection.prepareStatement(\"delete from student where f_id = 20\").executeUpdate();\n\t\t\t\t} catch (SQLException e) {\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t} finally {\n\t\t\t\t\t\tif (connection != null) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tconnection.close();\n\t\t\t\t\t\t\t\t} catch (SQLException e) {\n\t\t\t\t\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n ---\n \n # ** DRUID**\n \n ## 1.硬编码方式\n \n \n ## 2.配置方式\n\t\n\t","source":"_posts/2017_three_pools.md","raw":"---\ntitle: dbcp,c3p0,druid简单实例(包含配置介绍)\n\ncomments: true \n\ntags: \n - DBCP\n - C3P0\n - DRUID\n\ncategories: \n - 数据库连接池\n\ndescription: \n\ndate: 2017-02-13\n \n---\n\n# **三种连接池的配置:dbcp、c3p0、druid**\n\n\n\n# dbcp\n ## 1.硬编码方式\n \n @Test\n public void testDBCPCode() {\n //BasicDataSource实现接口DataSource DBCP连接池核心类\n BasicDataSource dataSouce = new BasicDataSource();\n dataSouce.setDriverClassName(\"com.mysql.jdbc.Driver\"); //驱动\n dataSouce.setUrl(\"jdbc:mysql://127.0.0.1:3306/test\"); //数据库连接字符串\n dataSouce.setUsername(\"root\"); //数据库用户名\n dataSouce.setPassword(\"anyao112233\"); //数据库密码\n dataSouce.setInitialSize(5); //设置初始化连接数\n dataSouce.setMaxTotal(5); //设置最大连接数\n dataSouce.setMaxWaitMillis(10000); //设置申请连接最大等待时间\n dataSouce.setRemoveAbandonedTimeout(60); //设置空闲连接时长 超过就回收没用的连接\n Connection connection = null;\n try {\n connection = dataSouce.getConnection();\n // connection.prepareStatement(\"update student set f_name = 'fuk' where f_id = 10\").executeUpdate();\n System.out.println(connection.getClass() + \"\\n\" + connection.getMetaData() + \"\\n\");\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n // 关闭\n if (connection != null) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n\n ## 2.配置方式\n *文件jdbc.properties*\n \n #驱动\n driverClassName=com.mysql.jdbc.Driver\n #数据库连接地址\n url=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n #用户名\n username=root\n #密码\n password=anyao112233\n #初始化连接\n initialSize=10\n #连接池的最大数据库连接数。设为0表示无限制\n maxTotal=50\n ##最小空闲连接\n minIdle=10\n #最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n #空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\n maxIdle=50\n #超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\n removeAbandonedTimeout=60\n #超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\n removeAbandoned=true\n #最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\n maxWaitMillis=60000\n #在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\n timeBetweenEvictionRunsMillis=60000\n #连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\n minEvictableIdleTimeMillis=300000\n \n\n\n @Test\n public void testDBCPXML() throws Exception {\n Connection connection = null;\n try {\n Properties prop = new Properties();\n InputStream inStream = this.getClass().getClassLoader()\n .getResourceAsStream(\"jdbc.properties\");\n prop.load(inStream);\n System.out.println(prop);\n // 根据prop配置,直接创建数据源对象(BasicDataSourceFactory工厂)\n DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);\n // 获取连接\n connection = dataSouce.getConnection();\n // connection.prepareStatement(\"delete from student where f_id=1\").executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n // 关闭\n if (connection != null) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n \n ---\n \n # ** C3P0**\n \n ## 1.硬编码方式\n \n @Test\n public void testC3P0Code() throws Exception {\n // 创建连接池核心工具类\n ComboPooledDataSource dataSource = new ComboPooledDataSource();\n \n dataSource.setJdbcUrl(\"jdbc:mysql://localhost:3306/test\");\n dataSource.setDriverClass(\"com.mysql.jdbc.Driver\");\n dataSource.setUser(\"root\");\n dataSource.setPassword(\"anyao112233\");\n dataSource.setInitialPoolSize(3);//连接池初始化时创建的连接数\n dataSource.setMaxPoolSize(6);//连接池中拥有的最大连接数\n dataSource.setMaxIdleTime(1000);\n Connection connection = null;\n try {\n connection = dataSource.getConnection();\n connection.prepareStatement(\"delete from student where f_name like '%name%'\").executeUpdate();\n } catch (SQLException e) {\n e.printStackTrace();\n } finally {\n if (connection != null) {\n try {\n connection.close();\n } catch (SQLException e) {\n e.printStackTrace();\n }\n }\n }\n }\n \n \n ## 2.配置方式\n \n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n root\n anyao112233\n com.mysql.jdbc.Driver\n jdbc:mysql://localhost:3306/test\n 10\n 30\n 100\n 10\n \n \n\t\n\t\n\t\t@Test\n\t\tpublic void testC3P0XML() throws Exception {\n\t\t\t\t// 创建c3p0连接池核心工具类\n\t\t\t\t// 自动加载src下c3p0的配置文件【c3p0-config.xml】\n\t\t\t\t//如果要使用default-config无需传参数,\n\t\t\t\t//如果要使用named-config里面配置初始化数据源,则只要使用一个带参数的ComboPooledDataSource构造器就可以了\n\t\t\t\tDataSource dataSource = new ComboPooledDataSource(\"myApp\");\n\t\t\t\tConnection connection = null;\n\t\t\t\ttry {\n\t\t\t\t\t\tconnection = dataSource.getConnection();\n\t\t\t\t\t\tconnection.prepareStatement(\"delete from student where f_id = 20\").executeUpdate();\n\t\t\t\t} catch (SQLException e) {\n\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t} finally {\n\t\t\t\t\t\tif (connection != null) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tconnection.close();\n\t\t\t\t\t\t\t\t} catch (SQLException e) {\n\t\t\t\t\t\t\t\t\t\te.printStackTrace();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n ---\n \n # ** DRUID**\n \n ## 1.硬编码方式\n \n \n ## 2.配置方式\n\t\n\t","slug":"2017_three_pools","published":1,"updated":"2018-01-13T02:29:22.262Z","layout":"post","photos":[],"link":"","_id":"ckm3inveg008f24uj3je6tlfh","content":"

三种连接池的配置:dbcp、c3p0、druid

\n

dbcp

1.硬编码方式

   @Test\n    public void testDBCPCode() {\n        //BasicDataSource实现接口DataSource   DBCP连接池核心类\n        BasicDataSource dataSouce = new BasicDataSource();\n        dataSouce.setDriverClassName("com.mysql.jdbc.Driver");  //驱动\n        dataSouce.setUrl("jdbc:mysql://127.0.0.1:3306/test");   //数据库连接字符串\n        dataSouce.setUsername("root");                          //数据库用户名\n        dataSouce.setPassword("anyao112233");                   //数据库密码\n        dataSouce.setInitialSize(5);                            //设置初始化连接数\n        dataSouce.setMaxTotal(5);                               //设置最大连接数\n        dataSouce.setMaxWaitMillis(10000);                      //设置申请连接最大等待时间\n        dataSouce.setRemoveAbandonedTimeout(60);                //设置空闲连接时长 超过就回收没用的连接\n        Connection connection = null;\n        try {\n            connection = dataSouce.getConnection();\n//            connection.prepareStatement("update student set f_name = 'fuk' where f_id = 10").executeUpdate();\n            System.out.println(connection.getClass() + "\\n" + connection.getMetaData() + "\\n");\n        } catch (SQLException e) {\n            e.printStackTrace();\n        } finally {\n            // 关闭\n            if (connection != null) {\n                try {\n                    connection.close();\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n

2.配置方式

文件jdbc.properties

\n
#驱动\ndriverClassName=com.mysql.jdbc.Driver\n#数据库连接地址\nurl=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n#用户名\nusername=root\n#密码\npassword=anyao112233\n#初始化连接\ninitialSize=10\n#连接池的最大数据库连接数。设为0表示无限制\nmaxTotal=50\n##最小空闲连接\nminIdle=10\n#最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n#空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\nmaxIdle=50\n#超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\nremoveAbandonedTimeout=60\n#超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\nremoveAbandoned=true\n#最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\nmaxWaitMillis=60000\n#在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\ntimeBetweenEvictionRunsMillis=60000\n#连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\nminEvictableIdleTimeMillis=300000\n\n\n\n   @Test\n    public void testDBCPXML() throws Exception {\n        Connection connection = null;\n        try {\n            Properties prop = new Properties();\n            InputStream inStream = this.getClass().getClassLoader()\n                        .getResourceAsStream("jdbc.properties");\n            prop.load(inStream);\n            System.out.println(prop);\n            // 根据prop配置,直接创建数据源对象(BasicDataSourceFactory工厂)\n            DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);\n            // 获取连接\n            connection = dataSouce.getConnection();\n//            connection.prepareStatement("delete from student where f_id=1").executeUpdate();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        } finally {\n            // 关闭\n            if (connection != null) {\n                try {\n                    connection.close();\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n

\n

C3P0

1.硬编码方式

@Test\n public void testC3P0Code() throws Exception {\n     // 创建连接池核心工具类\n     ComboPooledDataSource dataSource = new ComboPooledDataSource();\n\n     dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");\n     dataSource.setDriverClass("com.mysql.jdbc.Driver");\n     dataSource.setUser("root");\n     dataSource.setPassword("anyao112233");\n     dataSource.setInitialPoolSize(3);//连接池初始化时创建的连接数\n     dataSource.setMaxPoolSize(6);//连接池中拥有的最大连接数\n     dataSource.setMaxIdleTime(1000);\n     Connection connection = null;\n     try {\n         connection = dataSource.getConnection();\n         connection.prepareStatement("delete from student where f_name like '%name%'").executeUpdate();\n     } catch (SQLException e) {\n         e.printStackTrace();\n     } finally {\n         if (connection != null) {\n             try {\n                 connection.close();\n             } catch (SQLException e) {\n                 e.printStackTrace();\n             }\n         }\n     }\n }\n

2.配置方式

<c3p0-config>\n    <default-config>\n            <property name="user">root</property>\n            <property name="password">anyao112233</property>\n            <property name="driverClass">com.mysql.jdbc.Driver</property>\n            <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n            <property name="initialPoolSize">10</property>\n            <property name="maxIdleTime">30</property>\n            <property name="maxPoolSize">100</property>\n            <property name="minPoolSize">10</property>\n    </default-config>\n    <named-config name="myApp">\n            <property name="user">root</property>\n            <property name="password">anyao112233</property>\n            <property name="driverClass">com.mysql.jdbc.Driver</property>\n            <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n            <property name="initialPoolSize">10</property>\n            <property name="maxIdleTime">30</property>\n            <property name="maxPoolSize">100</property>\n            <property name="minPoolSize">10</property>\n    </named-config>\n</c3p0-config>  \n\n\n@Test\npublic void testC3P0XML() throws Exception {\n        // 创建c3p0连接池核心工具类\n        // 自动加载src下c3p0的配置文件【c3p0-config.xml】\n        //如果要使用default-config无需传参数,\n        //如果要使用named-config里面配置初始化数据源,则只要使用一个带参数的ComboPooledDataSource构造器就可以了\n        DataSource dataSource = new ComboPooledDataSource("myApp");\n        Connection connection = null;\n        try {\n                connection = dataSource.getConnection();\n                connection.prepareStatement("delete from student where f_id = 20").executeUpdate();\n        } catch (SQLException e) {\n                e.printStackTrace();\n        } finally {\n                if (connection != null) {\n                        try {\n                                connection.close();\n                        } catch (SQLException e) {\n                                e.printStackTrace();\n                        }\n                }\n        }\n    }\n

\n

DRUID

1.硬编码方式

2.配置方式

","site":{"data":{}},"excerpt":"

三种连接池的配置:dbcp、c3p0、druid

","more":"

dbcp

1.硬编码方式

   @Test\n    public void testDBCPCode() {\n        //BasicDataSource实现接口DataSource   DBCP连接池核心类\n        BasicDataSource dataSouce = new BasicDataSource();\n        dataSouce.setDriverClassName("com.mysql.jdbc.Driver");  //驱动\n        dataSouce.setUrl("jdbc:mysql://127.0.0.1:3306/test");   //数据库连接字符串\n        dataSouce.setUsername("root");                          //数据库用户名\n        dataSouce.setPassword("anyao112233");                   //数据库密码\n        dataSouce.setInitialSize(5);                            //设置初始化连接数\n        dataSouce.setMaxTotal(5);                               //设置最大连接数\n        dataSouce.setMaxWaitMillis(10000);                      //设置申请连接最大等待时间\n        dataSouce.setRemoveAbandonedTimeout(60);                //设置空闲连接时长 超过就回收没用的连接\n        Connection connection = null;\n        try {\n            connection = dataSouce.getConnection();\n//            connection.prepareStatement("update student set f_name = 'fuk' where f_id = 10").executeUpdate();\n            System.out.println(connection.getClass() + "\\n" + connection.getMetaData() + "\\n");\n        } catch (SQLException e) {\n            e.printStackTrace();\n        } finally {\n            // 关闭\n            if (connection != null) {\n                try {\n                    connection.close();\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n

2.配置方式

文件jdbc.properties

\n
#驱动\ndriverClassName=com.mysql.jdbc.Driver\n#数据库连接地址\nurl=jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8\n#用户名\nusername=root\n#密码\npassword=anyao112233\n#初始化连接\ninitialSize=10\n#连接池的最大数据库连接数。设为0表示无限制\nmaxTotal=50\n##最小空闲连接\nminIdle=10\n#最大空闲数,数据库连接的最大空闲数。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制\n#空闲连接:意思就是连接了数据库而最大的没有向数据库发送请求的连接\nmaxIdle=50\n#超过时间限制,回收没有用(废弃)的连接(默认为 300秒) 以秒为单位\nremoveAbandonedTimeout=60\n#超过removeAbandonedTimeout时间后,是否进行没用连接(废弃)的回收(默认为false,调整为true)\nremoveAbandoned=true\n#最大建立连接等待时间 超过此时间将异常 设为-1表示无限制 以毫秒为单位\nmaxWaitMillis=60000\n#在空闲连接回收器线程运行期间休眠的时间值,以毫秒为单位. 如果设置为非正数,则不运行空闲连接回收器线程(每60秒运行一次空闲连接回收器)\ntimeBetweenEvictionRunsMillis=60000\n#连接在池中保持空闲而不被空闲连接回收器线程(如果有)回收的最小时间值,单位毫秒(池中的连接空闲30s后被回收,默认值就是30分钟)\nminEvictableIdleTimeMillis=300000\n\n\n\n   @Test\n    public void testDBCPXML() throws Exception {\n        Connection connection = null;\n        try {\n            Properties prop = new Properties();\n            InputStream inStream = this.getClass().getClassLoader()\n                        .getResourceAsStream("jdbc.properties");\n            prop.load(inStream);\n            System.out.println(prop);\n            // 根据prop配置,直接创建数据源对象(BasicDataSourceFactory工厂)\n            DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);\n            // 获取连接\n            connection = dataSouce.getConnection();\n//            connection.prepareStatement("delete from student where f_id=1").executeUpdate();\n        } catch (SQLException e) {\n            e.printStackTrace();\n        } finally {\n            // 关闭\n            if (connection != null) {\n                try {\n                    connection.close();\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n

\n

C3P0

1.硬编码方式

@Test\n public void testC3P0Code() throws Exception {\n     // 创建连接池核心工具类\n     ComboPooledDataSource dataSource = new ComboPooledDataSource();\n\n     dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");\n     dataSource.setDriverClass("com.mysql.jdbc.Driver");\n     dataSource.setUser("root");\n     dataSource.setPassword("anyao112233");\n     dataSource.setInitialPoolSize(3);//连接池初始化时创建的连接数\n     dataSource.setMaxPoolSize(6);//连接池中拥有的最大连接数\n     dataSource.setMaxIdleTime(1000);\n     Connection connection = null;\n     try {\n         connection = dataSource.getConnection();\n         connection.prepareStatement("delete from student where f_name like '%name%'").executeUpdate();\n     } catch (SQLException e) {\n         e.printStackTrace();\n     } finally {\n         if (connection != null) {\n             try {\n                 connection.close();\n             } catch (SQLException e) {\n                 e.printStackTrace();\n             }\n         }\n     }\n }\n

2.配置方式

<c3p0-config>\n    <default-config>\n            <property name="user">root</property>\n            <property name="password">anyao112233</property>\n            <property name="driverClass">com.mysql.jdbc.Driver</property>\n            <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n            <property name="initialPoolSize">10</property>\n            <property name="maxIdleTime">30</property>\n            <property name="maxPoolSize">100</property>\n            <property name="minPoolSize">10</property>\n    </default-config>\n    <named-config name="myApp">\n            <property name="user">root</property>\n            <property name="password">anyao112233</property>\n            <property name="driverClass">com.mysql.jdbc.Driver</property>\n            <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>\n            <property name="initialPoolSize">10</property>\n            <property name="maxIdleTime">30</property>\n            <property name="maxPoolSize">100</property>\n            <property name="minPoolSize">10</property>\n    </named-config>\n</c3p0-config>  \n\n\n@Test\npublic void testC3P0XML() throws Exception {\n        // 创建c3p0连接池核心工具类\n        // 自动加载src下c3p0的配置文件【c3p0-config.xml】\n        //如果要使用default-config无需传参数,\n        //如果要使用named-config里面配置初始化数据源,则只要使用一个带参数的ComboPooledDataSource构造器就可以了\n        DataSource dataSource = new ComboPooledDataSource("myApp");\n        Connection connection = null;\n        try {\n                connection = dataSource.getConnection();\n                connection.prepareStatement("delete from student where f_id = 20").executeUpdate();\n        } catch (SQLException e) {\n                e.printStackTrace();\n        } finally {\n                if (connection != null) {\n                        try {\n                                connection.close();\n                        } catch (SQLException e) {\n                                e.printStackTrace();\n                        }\n                }\n        }\n    }\n

\n

DRUID

1.硬编码方式

2.配置方式

"},{"title":"使用多种算法对泰坦尼克号乘客获救原因进行分析","comments":1,"description":"使用多种算法对泰坦尼克号乘客获救原因进行分析","toc":true,"_content":"\n```python\nimport pandas\ntitanic = pandas.read_csv('titanic_train.csv')\ntitanic.head(20)\n```\n\n```python\n# 数据简单统计\ntitanic.describe()\n```\n\n```python\n# 由表Age列有部分缺失,影响最终模型效果\n# 解决方法: 填充中位数 (使用fillna()方法填充缺失值NaN, 使用median()获得中位数/中值)\ntitanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())\ntitanic.describe()\n```\n\n```python\n# 处理原始数据,其中有的是字符量需要映射为数值\nprint (titanic['Sex'].unique())\ntitanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0\ntitanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1\n\nprint (titanic['Embarked'].unique())\ntitanic['Embarked'] = titanic['Embarked'].fillna('S')# 缺失值填充,填充数量最多的类别\ntitanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0\ntitanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1\ntitanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2\n\ntitanic.head()\n```\n\n### 线性回归 步骤\n\n1. 观察数据,填充缺失值,改变数据形式(数据映射)\n2. 使用交叉验证减少过拟合风险\n3. 使用线性回归算法计算预测值\n4. 确定阈值,计算精度\n\n```python\n# 线性回归\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.cross_validation import KFold\n\n# 要使用的特征 船仓等级、性别、年龄、兄弟姐妹人数、老人孩子人数、船票价格、上船位置\npredictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']\n\n# 构建线性回归\nalg = LinearRegression()\nkf = KFold(titanic.shape[0], n_folds=3, random_state=0)\n\npredictions0 = []\nfor train, test in kf:\n # print (train.shape, test.shape)\n train_predictors = titanic.loc[train, predictors]\n train_targets = titanic.loc[train, ['Survived']]\n print (train_predictors.shape, train_targets.shape)\n test_features = titanic.loc[test, predictors]\n \n # 训练 拟合数据 使用交叉验证拆分出来的训练集\n alg.fit(train_predictors, train_targets)\n # 验证 使用交叉验证拆分出来的验证集\n test_predictions = alg.predict(test_features)\n \n predictions0.append(test_predictions)\n\n# 三组预测值\nprint (len(predictions0), '\\n\\n')\n\nimport numpy as np\n# 设定一个阈值 大于阈值表示获救 小于阈值表示未获救\npredictions = np.concatenate(predictions0, axis=0)\n\npredictions[predictions > 0.5] = 1\npredictions[predictions <= 0.5] = 0\n\nprint (type(predictions), type(titanic['Survived'].values))\nprint (predictions.shape,titanic['Survived'].values.reshape(-1, 1).shape)\n\naccuracy = len(predictions[predictions == titanic['Survived'].values.reshape(-1, 1)]) / len(predictions)\nprint (accuracy)\n```\n\n```python\n# 随机森林\nfrom sklearn import cross_validation\nfrom sklearn.ensemble import RandomForestClassifier\n\npredictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']\n```\n\n```python\n# 构造随机森林\n# n_estimators:构造的树个数 min_samples_split:最小切分点 min_samples_leaf:叶子节点最小个数\nalg = RandomForestClassifier(random_state=1, n_estimators=10, min_samples_split=2, min_samples_leaf=1)\n# 交叉验证\nkf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)\n# 准确率\nscores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)\n# 平均准确率\nprint (scores.mean())\n```\n\n```python\n# 随机森林调优: 修改参数 \n'''\n> RandomForestClassifier参数:\n1. max_features:随机森林允许单个决策树使用特征的最大数量, 增加max_features一般能提高模型的性能,同时降低算法的速度\n2. n_estimators:在利用最大投票数或平均值来预测之前,想要建立子树的数量。较多的子树可以让模型有更好的性能,但同时让你的代码变慢\n3. min_sample_leaf:最小样本叶片大小\n'''\n#1 构建随机森林模型(分类器)\nalg = RandomForestClassifier(random_state=1, n_estimators=50, min_samples_split=4, min_samples_leaf=10)\n#2 构建交叉验证\nkf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)\n\n'''\n> cross_validation.cross_val_score参数:\n1. alg:分类器,可以是任何的分类器,比如支持向量机分类器。alg = svm.SVC(kernel='linear', C=1)\n2. cv:交叉验证(cross validation)方法,如果cv是一个int数字的话,并且如果提供了raw target参数,那么就代表使用StratifiedKFold分类方式,如果没有提供raw target参数,那么就代表使用KFold分类方式。\n3. raw data,raw target:验证使用的数据(feature以及label)\n\n4. 返回值:对于每次不同的的划分raw data时,在test data(验证集)上得到的分类的准确率\n'''\n#3 进行交叉验证\nscores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)\nprint (len(scores), '-----', scores.mean(), '-----', scores)\n```\n\n```python\n# 随机森林调优: 添加特征\ntitanic['FamilySize'] = titanic['SibSp'] + titanic['Parch']\ntitanic['NameLength'] = titanic['Name'].apply(lambda x: len(x))\n```\n\n```python\n# 特征重要程度\nimport numpy as np\nfrom sklearn.feature_selection import SelectKBest,f_classif\nimport matplotlib.pyplot as plt\npredictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'FamilySize', 'NameLength']\n```\n\n### Univariate feature selection:单变量的特征选择\n单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要。剔除那些不重要的指标。\n\nsklearn.feature_selection模块中主要有以下几个方法:\n\n> SelectKBest和SelectPercentile比较相似,前者选择排名排在前n个的变量,后者选择排名排在前n%的变量。而他们通过什么指标来给变量排名呢?这需要二外的指定。\n\n> 对于regression问题,可以使用f_regression指标。对于classification问题,可以使用chi2或者f_classif变量。\n\n*使用的例子:*\n```python\nfrom sklearn.feature_selectionimport SelectPercentile, f_classif\nselector =SelectPercentile(f_classif, percentile=10)\n```\n\n### Recursive feature elimination:循环特征选择\n不单独的检验某个变量的价值,而是将其聚集在一起检验。它的基本思想是,对于一个数量为d的feature的集合,他的所有的子集的个数是2的d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validationerror。选择error最小的那个子集作为所挑选的特征。\n\n由以下两个方法实现:sklearn.feature_selection.RFE,sklearn.feature_selection.RFECV\n\n### L1-based featureselection:\n该思路的原理是:在linearregression模型中,有的时候会得到sparsesolution。意思是说很多变量前面的系数都等于0或者接近于0。这说明这些变量不重要,那么可以将这些变量去除。\n\n### Tree-based featureselection:决策树特征选择\n基于决策树算法做出特征选择\n\n```python\nselector = SelectKBest(f_classif, k=5)\nselector.fit(titanic[predictors], titanic['Survived'])\n\nscores = -np.log10(selector.pvalues_)\nprint (scores)\n\nplt.bar(range(len(predictors)), scores)\nplt.xticks(range(len(predictors)), predictors, rotation='vertical')\nplt.show()\n```\n\n```python\n# 多种算法集成预测\nfrom sklearn.ensemble import GradientBoostingClassifier\nfrom sklearn.linear_model import LogisticRegression\nimport numpy as np\n\n# 两个算法集成到一起使用\nalgorithms = [\n [GradientBoostingClassifier(random_state=1, n_estimators=25, max_depth=3), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked', 'FamilySize']],\n [LogisticRegression(random_state=1), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked']]\n]\n\nkf = KFold(titanic.shape[0], n_folds=3, random_state=1)\n\npredictions = []\n\n# 两个算法分别预测\nfor train, test in kf:\n train_target = titanic.loc[train, ['Survived']]\n full_test_predictions = []\n \n # 循环分类算法,获得不同算法求得的概率\n for alg, predictors in algorithms:\n # alg:分类算法 predictors:特征\n \n # 使用训练集 拟合数据\n alg.fit(titanic.loc[train, predictors], train_target)\n # 使用验证集 计算 (这里 直接取预测的概率的第二个值-->[:, 1])\n test_predictions = alg.predict_proba(titanic.loc[test, predictors].astype(float))[:, 1]\n full_test_predictions.append(test_predictions)\n \n # 使用不同的分类算法之后计算平均概率\n test_predictions = (full_test_predictions[0] + full_test_predictions[1]) / 2\n test_predictions[test_predictions <= 0.5] = 0\n test_predictions[test_predictions > 0.5] = 1\n \n predictions.append(test_predictions)\n\n# 获得的分类结果\npredictions = np.concatenate(predictions, axis=0)\n\n# 计算精确度\naccuracy = len(predictions[predictions == titanic['Survived']]) / len(predictions)\nprint (accuracy)\n```\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/2018031301.md","raw":"---\ntitle: 使用多种算法对泰坦尼克号乘客获救原因进行分析\n\ncomments: true \n\ntags: \n - python\n - 机器学习\n\ncategories: \n - 机器学习\n\ndescription: 使用多种算法对泰坦尼克号乘客获救原因进行分析\n\ntoc: true\n \n---\n\n```python\nimport pandas\ntitanic = pandas.read_csv('titanic_train.csv')\ntitanic.head(20)\n```\n\n```python\n# 数据简单统计\ntitanic.describe()\n```\n\n```python\n# 由表Age列有部分缺失,影响最终模型效果\n# 解决方法: 填充中位数 (使用fillna()方法填充缺失值NaN, 使用median()获得中位数/中值)\ntitanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())\ntitanic.describe()\n```\n\n```python\n# 处理原始数据,其中有的是字符量需要映射为数值\nprint (titanic['Sex'].unique())\ntitanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0\ntitanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1\n\nprint (titanic['Embarked'].unique())\ntitanic['Embarked'] = titanic['Embarked'].fillna('S')# 缺失值填充,填充数量最多的类别\ntitanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0\ntitanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1\ntitanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2\n\ntitanic.head()\n```\n\n### 线性回归 步骤\n\n1. 观察数据,填充缺失值,改变数据形式(数据映射)\n2. 使用交叉验证减少过拟合风险\n3. 使用线性回归算法计算预测值\n4. 确定阈值,计算精度\n\n```python\n# 线性回归\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.cross_validation import KFold\n\n# 要使用的特征 船仓等级、性别、年龄、兄弟姐妹人数、老人孩子人数、船票价格、上船位置\npredictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']\n\n# 构建线性回归\nalg = LinearRegression()\nkf = KFold(titanic.shape[0], n_folds=3, random_state=0)\n\npredictions0 = []\nfor train, test in kf:\n # print (train.shape, test.shape)\n train_predictors = titanic.loc[train, predictors]\n train_targets = titanic.loc[train, ['Survived']]\n print (train_predictors.shape, train_targets.shape)\n test_features = titanic.loc[test, predictors]\n \n # 训练 拟合数据 使用交叉验证拆分出来的训练集\n alg.fit(train_predictors, train_targets)\n # 验证 使用交叉验证拆分出来的验证集\n test_predictions = alg.predict(test_features)\n \n predictions0.append(test_predictions)\n\n# 三组预测值\nprint (len(predictions0), '\\n\\n')\n\nimport numpy as np\n# 设定一个阈值 大于阈值表示获救 小于阈值表示未获救\npredictions = np.concatenate(predictions0, axis=0)\n\npredictions[predictions > 0.5] = 1\npredictions[predictions <= 0.5] = 0\n\nprint (type(predictions), type(titanic['Survived'].values))\nprint (predictions.shape,titanic['Survived'].values.reshape(-1, 1).shape)\n\naccuracy = len(predictions[predictions == titanic['Survived'].values.reshape(-1, 1)]) / len(predictions)\nprint (accuracy)\n```\n\n```python\n# 随机森林\nfrom sklearn import cross_validation\nfrom sklearn.ensemble import RandomForestClassifier\n\npredictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']\n```\n\n```python\n# 构造随机森林\n# n_estimators:构造的树个数 min_samples_split:最小切分点 min_samples_leaf:叶子节点最小个数\nalg = RandomForestClassifier(random_state=1, n_estimators=10, min_samples_split=2, min_samples_leaf=1)\n# 交叉验证\nkf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)\n# 准确率\nscores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)\n# 平均准确率\nprint (scores.mean())\n```\n\n```python\n# 随机森林调优: 修改参数 \n'''\n> RandomForestClassifier参数:\n1. max_features:随机森林允许单个决策树使用特征的最大数量, 增加max_features一般能提高模型的性能,同时降低算法的速度\n2. n_estimators:在利用最大投票数或平均值来预测之前,想要建立子树的数量。较多的子树可以让模型有更好的性能,但同时让你的代码变慢\n3. min_sample_leaf:最小样本叶片大小\n'''\n#1 构建随机森林模型(分类器)\nalg = RandomForestClassifier(random_state=1, n_estimators=50, min_samples_split=4, min_samples_leaf=10)\n#2 构建交叉验证\nkf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)\n\n'''\n> cross_validation.cross_val_score参数:\n1. alg:分类器,可以是任何的分类器,比如支持向量机分类器。alg = svm.SVC(kernel='linear', C=1)\n2. cv:交叉验证(cross validation)方法,如果cv是一个int数字的话,并且如果提供了raw target参数,那么就代表使用StratifiedKFold分类方式,如果没有提供raw target参数,那么就代表使用KFold分类方式。\n3. raw data,raw target:验证使用的数据(feature以及label)\n\n4. 返回值:对于每次不同的的划分raw data时,在test data(验证集)上得到的分类的准确率\n'''\n#3 进行交叉验证\nscores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)\nprint (len(scores), '-----', scores.mean(), '-----', scores)\n```\n\n```python\n# 随机森林调优: 添加特征\ntitanic['FamilySize'] = titanic['SibSp'] + titanic['Parch']\ntitanic['NameLength'] = titanic['Name'].apply(lambda x: len(x))\n```\n\n```python\n# 特征重要程度\nimport numpy as np\nfrom sklearn.feature_selection import SelectKBest,f_classif\nimport matplotlib.pyplot as plt\npredictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'FamilySize', 'NameLength']\n```\n\n### Univariate feature selection:单变量的特征选择\n单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要。剔除那些不重要的指标。\n\nsklearn.feature_selection模块中主要有以下几个方法:\n\n> SelectKBest和SelectPercentile比较相似,前者选择排名排在前n个的变量,后者选择排名排在前n%的变量。而他们通过什么指标来给变量排名呢?这需要二外的指定。\n\n> 对于regression问题,可以使用f_regression指标。对于classification问题,可以使用chi2或者f_classif变量。\n\n*使用的例子:*\n```python\nfrom sklearn.feature_selectionimport SelectPercentile, f_classif\nselector =SelectPercentile(f_classif, percentile=10)\n```\n\n### Recursive feature elimination:循环特征选择\n不单独的检验某个变量的价值,而是将其聚集在一起检验。它的基本思想是,对于一个数量为d的feature的集合,他的所有的子集的个数是2的d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validationerror。选择error最小的那个子集作为所挑选的特征。\n\n由以下两个方法实现:sklearn.feature_selection.RFE,sklearn.feature_selection.RFECV\n\n### L1-based featureselection:\n该思路的原理是:在linearregression模型中,有的时候会得到sparsesolution。意思是说很多变量前面的系数都等于0或者接近于0。这说明这些变量不重要,那么可以将这些变量去除。\n\n### Tree-based featureselection:决策树特征选择\n基于决策树算法做出特征选择\n\n```python\nselector = SelectKBest(f_classif, k=5)\nselector.fit(titanic[predictors], titanic['Survived'])\n\nscores = -np.log10(selector.pvalues_)\nprint (scores)\n\nplt.bar(range(len(predictors)), scores)\nplt.xticks(range(len(predictors)), predictors, rotation='vertical')\nplt.show()\n```\n\n```python\n# 多种算法集成预测\nfrom sklearn.ensemble import GradientBoostingClassifier\nfrom sklearn.linear_model import LogisticRegression\nimport numpy as np\n\n# 两个算法集成到一起使用\nalgorithms = [\n [GradientBoostingClassifier(random_state=1, n_estimators=25, max_depth=3), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked', 'FamilySize']],\n [LogisticRegression(random_state=1), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked']]\n]\n\nkf = KFold(titanic.shape[0], n_folds=3, random_state=1)\n\npredictions = []\n\n# 两个算法分别预测\nfor train, test in kf:\n train_target = titanic.loc[train, ['Survived']]\n full_test_predictions = []\n \n # 循环分类算法,获得不同算法求得的概率\n for alg, predictors in algorithms:\n # alg:分类算法 predictors:特征\n \n # 使用训练集 拟合数据\n alg.fit(titanic.loc[train, predictors], train_target)\n # 使用验证集 计算 (这里 直接取预测的概率的第二个值-->[:, 1])\n test_predictions = alg.predict_proba(titanic.loc[test, predictors].astype(float))[:, 1]\n full_test_predictions.append(test_predictions)\n \n # 使用不同的分类算法之后计算平均概率\n test_predictions = (full_test_predictions[0] + full_test_predictions[1]) / 2\n test_predictions[test_predictions <= 0.5] = 0\n test_predictions[test_predictions > 0.5] = 1\n \n predictions.append(test_predictions)\n\n# 获得的分类结果\npredictions = np.concatenate(predictions, axis=0)\n\n# 计算精确度\naccuracy = len(predictions[predictions == titanic['Survived']]) / len(predictions)\nprint (accuracy)\n```\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"2018031301","published":1,"date":"2020-01-15T05:50:39.657Z","updated":"2021-03-10T13:50:15.274Z","layout":"post","photos":[],"link":"","_id":"ckm3invei008i24ujplc3jv9r","content":"
1
2
3
import pandas
titanic = pandas.read_csv('titanic_train.csv')
titanic.head(20)
\n
1
2
# 数据简单统计
titanic.describe()
\n
1
2
3
4
# 由表Age列有部分缺失,影响最终模型效果
# 解决方法: 填充中位数 (使用fillna()方法填充缺失值NaN, 使用median()获得中位数/中值)
titanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())
titanic.describe()
\n
1
2
3
4
5
6
7
8
9
10
11
12
# 处理原始数据,其中有的是字符量需要映射为数值
print (titanic['Sex'].unique())
titanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0
titanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1

print (titanic['Embarked'].unique())
titanic['Embarked'] = titanic['Embarked'].fillna('S')# 缺失值填充,填充数量最多的类别
titanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0
titanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1
titanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2

titanic.head()
\n

线性回归 步骤

    \n
  1. 观察数据,填充缺失值,改变数据形式(数据映射)
  2. \n
  3. 使用交叉验证减少过拟合风险
  4. \n
  5. 使用线性回归算法计算预测值
  6. \n
  7. 确定阈值,计算精度
  8. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 线性回归
from sklearn.linear_model import LinearRegression
from sklearn.cross_validation import KFold

# 要使用的特征 船仓等级、性别、年龄、兄弟姐妹人数、老人孩子人数、船票价格、上船位置
predictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']

# 构建线性回归
alg = LinearRegression()
kf = KFold(titanic.shape[0], n_folds=3, random_state=0)

predictions0 = []
for train, test in kf:
# print (train.shape, test.shape)
train_predictors = titanic.loc[train, predictors]
train_targets = titanic.loc[train, ['Survived']]
print (train_predictors.shape, train_targets.shape)
test_features = titanic.loc[test, predictors]

# 训练 拟合数据 使用交叉验证拆分出来的训练集
alg.fit(train_predictors, train_targets)
# 验证 使用交叉验证拆分出来的验证集
test_predictions = alg.predict(test_features)

predictions0.append(test_predictions)

# 三组预测值
print (len(predictions0), '\\n\\n')

import numpy as np
# 设定一个阈值 大于阈值表示获救 小于阈值表示未获救
predictions = np.concatenate(predictions0, axis=0)

predictions[predictions > 0.5] = 1
predictions[predictions <= 0.5] = 0

print (type(predictions), type(titanic['Survived'].values))
print (predictions.shape,titanic['Survived'].values.reshape(-1, 1).shape)

accuracy = len(predictions[predictions == titanic['Survived'].values.reshape(-1, 1)]) / len(predictions)
print (accuracy)
\n
1
2
3
4
5
# 随机森林
from sklearn import cross_validation
from sklearn.ensemble import RandomForestClassifier

predictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
\n
1
2
3
4
5
6
7
8
9
# 构造随机森林
# n_estimators:构造的树个数 min_samples_split:最小切分点 min_samples_leaf:叶子节点最小个数
alg = RandomForestClassifier(random_state=1, n_estimators=10, min_samples_split=2, min_samples_leaf=1)
# 交叉验证
kf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)
# 准确率
scores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)
# 平均准确率
print (scores.mean())
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 随机森林调优: 修改参数 
'''
> RandomForestClassifier参数:
1. max_features:随机森林允许单个决策树使用特征的最大数量, 增加max_features一般能提高模型的性能,同时降低算法的速度
2. n_estimators:在利用最大投票数或平均值来预测之前,想要建立子树的数量。较多的子树可以让模型有更好的性能,但同时让你的代码变慢
3. min_sample_leaf:最小样本叶片大小
'''
#1 构建随机森林模型(分类器)
alg = RandomForestClassifier(random_state=1, n_estimators=50, min_samples_split=4, min_samples_leaf=10)
#2 构建交叉验证
kf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)

'''
> cross_validation.cross_val_score参数:
1. alg:分类器,可以是任何的分类器,比如支持向量机分类器。alg = svm.SVC(kernel='linear', C=1)
2. cv:交叉验证(cross validation)方法,如果cv是一个int数字的话,并且如果提供了raw target参数,那么就代表使用StratifiedKFold分类方式,如果没有提供raw target参数,那么就代表使用KFold分类方式。
3. raw data,raw target:验证使用的数据(feature以及label)

4. 返回值:对于每次不同的的划分raw data时,在test data(验证集)上得到的分类的准确率
'''
#3 进行交叉验证
scores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)
print (len(scores), '-----', scores.mean(), '-----', scores)
\n
1
2
3
# 随机森林调优: 添加特征
titanic['FamilySize'] = titanic['SibSp'] + titanic['Parch']
titanic['NameLength'] = titanic['Name'].apply(lambda x: len(x))
\n
1
2
3
4
5
# 特征重要程度
import numpy as np
from sklearn.feature_selection import SelectKBest,f_classif
import matplotlib.pyplot as plt
predictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'FamilySize', 'NameLength']
\n

Univariate feature selection:单变量的特征选择

单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要。剔除那些不重要的指标。

\n

sklearn.feature_selection模块中主要有以下几个方法:

\n
\n

SelectKBest和SelectPercentile比较相似,前者选择排名排在前n个的变量,后者选择排名排在前n%的变量。而他们通过什么指标来给变量排名呢?这需要二外的指定。

\n
\n
\n

对于regression问题,可以使用f_regression指标。对于classification问题,可以使用chi2或者f_classif变量。

\n
\n

使用的例子:

1
2
from sklearn.feature_selectionimport SelectPercentile, f_classif
selector =SelectPercentile(f_classif, percentile=10)

\n

Recursive feature elimination:循环特征选择

不单独的检验某个变量的价值,而是将其聚集在一起检验。它的基本思想是,对于一个数量为d的feature的集合,他的所有的子集的个数是2的d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validationerror。选择error最小的那个子集作为所挑选的特征。

\n

由以下两个方法实现:sklearn.feature_selection.RFE,sklearn.feature_selection.RFECV

\n

L1-based featureselection:

该思路的原理是:在linearregression模型中,有的时候会得到sparsesolution。意思是说很多变量前面的系数都等于0或者接近于0。这说明这些变量不重要,那么可以将这些变量去除。

\n

Tree-based featureselection:决策树特征选择

基于决策树算法做出特征选择

\n
1
2
3
4
5
6
7
8
9
selector = SelectKBest(f_classif, k=5)
selector.fit(titanic[predictors], titanic['Survived'])

scores = -np.log10(selector.pvalues_)
print (scores)

plt.bar(range(len(predictors)), scores)
plt.xticks(range(len(predictors)), predictors, rotation='vertical')
plt.show()
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 多种算法集成预测
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
import numpy as np

# 两个算法集成到一起使用
algorithms = [
[GradientBoostingClassifier(random_state=1, n_estimators=25, max_depth=3), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked', 'FamilySize']],
[LogisticRegression(random_state=1), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked']]
]

kf = KFold(titanic.shape[0], n_folds=3, random_state=1)

predictions = []

# 两个算法分别预测
for train, test in kf:
train_target = titanic.loc[train, ['Survived']]
full_test_predictions = []

# 循环分类算法,获得不同算法求得的概率
for alg, predictors in algorithms:
# alg:分类算法 predictors:特征

# 使用训练集 拟合数据
alg.fit(titanic.loc[train, predictors], train_target)
# 使用验证集 计算 (这里 直接取预测的概率的第二个值-->[:, 1])
test_predictions = alg.predict_proba(titanic.loc[test, predictors].astype(float))[:, 1]
full_test_predictions.append(test_predictions)

# 使用不同的分类算法之后计算平均概率
test_predictions = (full_test_predictions[0] + full_test_predictions[1]) / 2
test_predictions[test_predictions <= 0.5] = 0
test_predictions[test_predictions > 0.5] = 1

predictions.append(test_predictions)

# 获得的分类结果
predictions = np.concatenate(predictions, axis=0)

# 计算精确度
accuracy = len(predictions[predictions == titanic['Survived']]) / len(predictions)
print (accuracy)
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"","more":"
1
2
3
import pandas
titanic = pandas.read_csv('titanic_train.csv')
titanic.head(20)
\n
1
2
# 数据简单统计
titanic.describe()
\n
1
2
3
4
# 由表Age列有部分缺失,影响最终模型效果
# 解决方法: 填充中位数 (使用fillna()方法填充缺失值NaN, 使用median()获得中位数/中值)
titanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())
titanic.describe()
\n
1
2
3
4
5
6
7
8
9
10
11
12
# 处理原始数据,其中有的是字符量需要映射为数值
print (titanic['Sex'].unique())
titanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0
titanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1

print (titanic['Embarked'].unique())
titanic['Embarked'] = titanic['Embarked'].fillna('S')# 缺失值填充,填充数量最多的类别
titanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0
titanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1
titanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2

titanic.head()
\n

线性回归 步骤

    \n
  1. 观察数据,填充缺失值,改变数据形式(数据映射)
  2. \n
  3. 使用交叉验证减少过拟合风险
  4. \n
  5. 使用线性回归算法计算预测值
  6. \n
  7. 确定阈值,计算精度
  8. \n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 线性回归
from sklearn.linear_model import LinearRegression
from sklearn.cross_validation import KFold

# 要使用的特征 船仓等级、性别、年龄、兄弟姐妹人数、老人孩子人数、船票价格、上船位置
predictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']

# 构建线性回归
alg = LinearRegression()
kf = KFold(titanic.shape[0], n_folds=3, random_state=0)

predictions0 = []
for train, test in kf:
# print (train.shape, test.shape)
train_predictors = titanic.loc[train, predictors]
train_targets = titanic.loc[train, ['Survived']]
print (train_predictors.shape, train_targets.shape)
test_features = titanic.loc[test, predictors]

# 训练 拟合数据 使用交叉验证拆分出来的训练集
alg.fit(train_predictors, train_targets)
# 验证 使用交叉验证拆分出来的验证集
test_predictions = alg.predict(test_features)

predictions0.append(test_predictions)

# 三组预测值
print (len(predictions0), '\\n\\n')

import numpy as np
# 设定一个阈值 大于阈值表示获救 小于阈值表示未获救
predictions = np.concatenate(predictions0, axis=0)

predictions[predictions > 0.5] = 1
predictions[predictions <= 0.5] = 0

print (type(predictions), type(titanic['Survived'].values))
print (predictions.shape,titanic['Survived'].values.reshape(-1, 1).shape)

accuracy = len(predictions[predictions == titanic['Survived'].values.reshape(-1, 1)]) / len(predictions)
print (accuracy)
\n
1
2
3
4
5
# 随机森林
from sklearn import cross_validation
from sklearn.ensemble import RandomForestClassifier

predictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
\n
1
2
3
4
5
6
7
8
9
# 构造随机森林
# n_estimators:构造的树个数 min_samples_split:最小切分点 min_samples_leaf:叶子节点最小个数
alg = RandomForestClassifier(random_state=1, n_estimators=10, min_samples_split=2, min_samples_leaf=1)
# 交叉验证
kf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)
# 准确率
scores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)
# 平均准确率
print (scores.mean())
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 随机森林调优: 修改参数 
'''
> RandomForestClassifier参数:
1. max_features:随机森林允许单个决策树使用特征的最大数量, 增加max_features一般能提高模型的性能,同时降低算法的速度
2. n_estimators:在利用最大投票数或平均值来预测之前,想要建立子树的数量。较多的子树可以让模型有更好的性能,但同时让你的代码变慢
3. min_sample_leaf:最小样本叶片大小
'''
#1 构建随机森林模型(分类器)
alg = RandomForestClassifier(random_state=1, n_estimators=50, min_samples_split=4, min_samples_leaf=10)
#2 构建交叉验证
kf = cross_validation.KFold(titanic.shape[0], n_folds=3, random_state=1)

'''
> cross_validation.cross_val_score参数:
1. alg:分类器,可以是任何的分类器,比如支持向量机分类器。alg = svm.SVC(kernel='linear', C=1)
2. cv:交叉验证(cross validation)方法,如果cv是一个int数字的话,并且如果提供了raw target参数,那么就代表使用StratifiedKFold分类方式,如果没有提供raw target参数,那么就代表使用KFold分类方式。
3. raw data,raw target:验证使用的数据(feature以及label)

4. 返回值:对于每次不同的的划分raw data时,在test data(验证集)上得到的分类的准确率
'''
#3 进行交叉验证
scores = cross_validation.cross_val_score(alg, titanic[predictors], titanic['Survived'], cv=kf)
print (len(scores), '-----', scores.mean(), '-----', scores)
\n
1
2
3
# 随机森林调优: 添加特征
titanic['FamilySize'] = titanic['SibSp'] + titanic['Parch']
titanic['NameLength'] = titanic['Name'].apply(lambda x: len(x))
\n
1
2
3
4
5
# 特征重要程度
import numpy as np
from sklearn.feature_selection import SelectKBest,f_classif
import matplotlib.pyplot as plt
predictors = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'FamilySize', 'NameLength']
\n

Univariate feature selection:单变量的特征选择

单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要。剔除那些不重要的指标。

\n

sklearn.feature_selection模块中主要有以下几个方法:

\n
\n

SelectKBest和SelectPercentile比较相似,前者选择排名排在前n个的变量,后者选择排名排在前n%的变量。而他们通过什么指标来给变量排名呢?这需要二外的指定。

\n
\n
\n

对于regression问题,可以使用f_regression指标。对于classification问题,可以使用chi2或者f_classif变量。

\n
\n

使用的例子:

1
2
from sklearn.feature_selectionimport SelectPercentile, f_classif
selector =SelectPercentile(f_classif, percentile=10)

\n

Recursive feature elimination:循环特征选择

不单独的检验某个变量的价值,而是将其聚集在一起检验。它的基本思想是,对于一个数量为d的feature的集合,他的所有的子集的个数是2的d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validationerror。选择error最小的那个子集作为所挑选的特征。

\n

由以下两个方法实现:sklearn.feature_selection.RFE,sklearn.feature_selection.RFECV

\n

L1-based featureselection:

该思路的原理是:在linearregression模型中,有的时候会得到sparsesolution。意思是说很多变量前面的系数都等于0或者接近于0。这说明这些变量不重要,那么可以将这些变量去除。

\n

Tree-based featureselection:决策树特征选择

基于决策树算法做出特征选择

\n
1
2
3
4
5
6
7
8
9
selector = SelectKBest(f_classif, k=5)
selector.fit(titanic[predictors], titanic['Survived'])

scores = -np.log10(selector.pvalues_)
print (scores)

plt.bar(range(len(predictors)), scores)
plt.xticks(range(len(predictors)), predictors, rotation='vertical')
plt.show()
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 多种算法集成预测
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
import numpy as np

# 两个算法集成到一起使用
algorithms = [
[GradientBoostingClassifier(random_state=1, n_estimators=25, max_depth=3), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked', 'FamilySize']],
[LogisticRegression(random_state=1), ['Pclass', 'Sex', 'Age', 'Fare', 'Embarked']]
]

kf = KFold(titanic.shape[0], n_folds=3, random_state=1)

predictions = []

# 两个算法分别预测
for train, test in kf:
train_target = titanic.loc[train, ['Survived']]
full_test_predictions = []

# 循环分类算法,获得不同算法求得的概率
for alg, predictors in algorithms:
# alg:分类算法 predictors:特征

# 使用训练集 拟合数据
alg.fit(titanic.loc[train, predictors], train_target)
# 使用验证集 计算 (这里 直接取预测的概率的第二个值-->[:, 1])
test_predictions = alg.predict_proba(titanic.loc[test, predictors].astype(float))[:, 1]
full_test_predictions.append(test_predictions)

# 使用不同的分类算法之后计算平均概率
test_predictions = (full_test_predictions[0] + full_test_predictions[1]) / 2
test_predictions[test_predictions <= 0.5] = 0
test_predictions[test_predictions > 0.5] = 1

predictions.append(test_predictions)

# 获得的分类结果
predictions = np.concatenate(predictions, axis=0)

# 计算精确度
accuracy = len(predictions[predictions == titanic['Survived']]) / len(predictions)
print (accuracy)
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n"},{"title":"Mybatis入门学习","comments":1,"description":null,"date":"2017-06-15T16:00:00.000Z","_content":"## 什么是 MyBatis ?\n> 官方文档: http://www.mybatis.org/mybatis-3/zh/index.html\n\n> MyBatis 一个基于Java的持久层框架; 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将代理接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。\n\n###### *与hibernate不同的是: mybatis是通过xml映射文件实现代理接口来实现操作数据库的功能*\n\n\n\n### 基本的步骤:\n#### 1. 引入dependency\n\n \n org.mybatis\n mybatis\n 3.4.1\n \n\n\n---\n#### 2. mybatis核心配置文件 #在resources目录下的 mybatis/conf.xml\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n*如果需要(或者测试没日志)可以引入log4j的包和配置文件 方便测试*\n---\n#### 3. 新建实体类--MyUser.java 对应数据库中表my_user \n\n //三个属性\n private int id;\n private String name;\n private int age;\n\n\n---\n#### 4. 新建Mapper映射文件--MyUserMapper.xml(要和实体放到一个目录下)\n\n \n \n \n \n \n \n\n\n*maven项目运行时找不到映射文件:Could not find resource;(原因是maven在构建的时候不会识别src下的配置文件,见生成的class目录)所以有两种方法解决*\n\n 1.在resources下新建目录,目录结构和java下的一致(因为需要保证映射文件和实体在同一个目录下),到时候生成的.class就会和配置文件放到一起,就可以找到了\n 2.(推荐), 添加设置资源目录: 在pom的build下加入:\n \n \n src/main/java\n \n **/*.xml\n \n false\n \n \n\n---\n#### 5.可以测试啦: 添加测试类--MainTest.java\n\n\n private SqlSession util(){\n //配置文件\n String resource = \"mybatis/conf.xml\";\n \n //加载配置mybatis文件\n InputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\n // Reader reader = Resources.getResourceAsReader(resource); //也可以使用这个加载配置\n //构建sqlSession工厂\n SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\n //得到能执行映射文件中sql语句的sqlSession(等同于jdbc中的PreparedStatement)\n SqlSession session = sqlSessionFactory.openSession(true);//设置自动提交事务\n \n return session;\n }\n \n @Test\n public void testSelect() {\n SqlSession session = util();\n //映射sql的标识符字符串(映射文件全类名 + 映射节点id)\n String statement = \"com.mybatis.test1.pojo.MyUserMapper.getUser\";\n //执行sql语句返回结果\n MyUser user = session.selectOne(statement, 12);//两个参数 statement和占位符要填写的参数\n \n System.out.println(user);\n }\n\n---\n\n#### 6. 大功告成 ! \n---\n#### 7. 其他操作 : CURD\n\n> 增删改查实现方法, 1. 增加映射文件内容; \n\n\n \n \n INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})\n \n \n \n \n DELETE FROM my_user\n WHERE id = #{id}\n \n \n \n \n UPDATE my_user\n SET name = #{name}, age = #{age}\n WHERE id = #{id}\n \n \n \n \n \n \n\n\n\n> 2.调用sqlSession的各种方法(方法名基本上是个人都能看出来干嘛的,你就直接试); 比如\n\n\n int result = session.delete(statement, 1);\n \n List users = session.selectList(statement);\n \n ....\n\n---\n#### 8. 基于接口的写法: \n\n> 基于接口有两种具体实现 1.基于注解:不需要自己写实现类,实现类自己\"生成\"; 2:基于xml文件,需要把xml文件和接口放在同一个目录下\n\n\n##### 基于注解\n1.新建一个代理接口--MyUserMapper.java\n\n public interface MyUserMapper {\n \n @Insert(\"INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})\")\n public int add(MyUser user);\n \n @Delete(\"DELETE FROM my_user WHERE id = #{id}\")\n public int del(int id);\n \n @Update(\"UPDATE my_user SET name = #{name}, age = #{age} WHERE id = #{id}\")\n public int edit(MyUser user);\n \n @Select(\"SELECT * FROM my_user WHERE id = #{id}\")\n public MyUser find(int id);\n \n @Select(\"SELECT * FROM my_user\")\n public List getAll();\n }\n\n\n2.\"注册\"到mybatis配置文件--在conf.xml中mappers节点下添加\n\n \n\n\n3.测试\n\n String resource = \"mybatis/conf.xml\";\n InputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\n SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\n SqlSession session = sqlSessionFactory.openSession(true);\n //获取接口动态产生的实现类 再调用方法\n MyUserMapper myUserMapper = session.getMapper(MyUserMapper.class);\n MyUser user = myUserMapper.find(12);\n \n System.out.println(user);\n \n\n---\n\n\n##### 基于配置文件\n1. 和实现xml文件--MyUserMapper.java & MyUserMapper.xml\n\n\n public interface MyUserMapper {\n \n //方法要求 类名必须与.xml名相同; 方法名必须与MyUserMapper.xml中对应的id相同; 并且参数要一一对应\n public List getAll();\n }\n \n \n\n\n2. \"注册\"到mybatis配置文件--在conf.xml中mappers节点下添加(二选一, 只要有一个就可定位Mapper)\n\n\n \n \n \n\n\n3. 测试(方法不变 只需改方法)\n\n---\n#### 9.优化\n\n> 1.数据库文件: 把数据库信息配置到一个文件: db.properties,然后在conf.xml中引入,调用使用EL表达式\n\n\n \n\n\n\n\n> 2.配置别名: 在映射文件中写全类名很长很麻烦,可以在conf.xml中配置别名 alias为别名; 则可以在映射xml文件中写别名表示此类\n\n\n \n \n \n \n\n> 2.配置别名2: 为整个包下类取别名 则别名为此类类名(比如: MyUser)\n\n\n \n \n \n \n---\n\n\n#### 10.对于数据表字段名和实体属性名不一致的问题\n\n\n> 当表字段名和实体属性名不同 就会无法获取数据(区分大小写), 对应的属性即为null(相关类型默认值) 原因是查到的数据无法映射到对应的result实体上,所以只要创建一个映射关系就能解决这个问题\n\n\n1. 方法一: 指定字段别名(sql语句的方法, 直接指定字段别名为实体属性名)\n\n\n \n\n\n\n2. 方法二: mybatis提供resultMap用于结果映射; like this\n\n\n \n \n \n \n \n \n \n \n \n\n\n---\n\n#### 11.一对一和一对多的实现\n\n> 对于涉及到多表查询, 一般有两种方式: 1.一个表一个表查询,用第一个表查到的数据组成第二个查询语句(也叫嵌套查询); 2.sql关联查询,一条语句,一次查询,语句比较复杂(也叫嵌套结果);\n\n>为了尽可能的减少代码量(当然,去掉不必要的\"体积\"的麻烦),而且效率上 嵌套结果>存储过程>嵌套查询;\n\n##### 嵌套结果示例:\n\n1. 一对一\n\n> 两个实体类以及一个结果封装类(由于不能仅仅用一个实体接收查询的所有的字段, so其用于封装查询的结果)\n\n // 对应数据库中表: order (字段有所不同,参考查询语句)\n public class Order {\n private int id;\n private String orderNo;\n private float orderPrice;\n private User user;\n \n ... //构造方法, setter, getter, toString等方法\n }\n \n // 对应数据库中表: user\n public class User {\n private int id;\n private String name;\n private int age;\n \n ...\n }\n \n // 结果封装类 id为user的id, 属性包含其他两个实体类(由于是一对一,则参数也可以把实体的参数复制过来,那查询mapper中resultMap有所不同)\n public class UserOrder {\n private String id;\n private User user;\n private Order order;\n \n ...\n }\n\n\n\n> 映射文件中查询的编写:\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n> 测试类:\n\n\n ...\n UserOrder order = orderMapper.getOrderInfo(12);\n System.out.println(order);\n\n\n1. 一对多\n\n> 一的一方同上,多的一方就需要一个新的类封装实体对象的集合,并且需要修改mapper写法\n\n\n // 实体类不变 结果封装类为:\n public class UserOrder2 {\n private String id;\n private User user;\n private List orders;\n }\n\n\n> 映射文件:\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n*高级结果映射详细简介见: http://zj2626.github.io/2017/06/19/20170619_Mybatis/*\n\n> 测试类\n\n UserOrder2 order2 = orderMapper.getOrderInfo2(12);\n System.out.println(order2);\n\n\n#### 12.一级缓存与二级缓存\n\n> 与hibernate相似, mybatis也存在缓存并且默认开启一级缓存,mybatis一级缓存是session级别的,而二级缓存是namespace(statement)级别的(即每个mapper文件就是一个二级缓存范围,需要配置)\n\n> 配置二级缓存\n\n \n \n or\n \n \n\n#### 13.与spring集成\n\n> 引入依赖\n\n \n org.mybatis\n mybatis-spring\n 1.3.0\n \n \n> 配置mapper 同上\n\n> 配置spring配置文件:\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n> 配置mybatis配置文件 里面没有配置内容(但是必须要)\n\n\n \n \n \n \n \n\n#### n.分页插件 --- PageHelper: 一个分页插件,支持多种数据库,原理大概是在执行sql语句之前(拦截器)进行了操作改写了sql语句,实现分页\n\n1. 导入依赖\n\n\n \n com.github.pagehelper\n pagehelper\n \n \n2. 配置插件--拦截器(在mybatis的配置文件中), 笔者在spring中集成了mybatis的配置\n\n\n \n \n \n \n \n \n \n \n3. 测试分页\n\n\n @Test\n public void testPage() {\n //初始化Spring容器\n ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:spring/applicationContext-dao.xml\");\n \n //获取代理对象\n TbItemMapper itemMapper = applicationContext.getBean(TbItemMapper.class);\n \n //执行sql语句前设置分页信息使用PageHelper的startPage方法\n PageHelper.startPage(1, 10);\n \n //查询\n TbItemExample example = new TbItemExample();\n List list = itemMapper.selectByExample(example);\n \n //取分页信息PageInfo 总记录数 总页数 当前页\n PageInfo pageInfo = new PageInfo<>(list);\n System.out.println(pageInfo.getTotal());\n System.out.println(pageInfo.getEndRow());\n System.out.println(pageInfo.getFirstPage());\n System.out.println(pageInfo.getLastPage());\n System.out.println(pageInfo.getList());\n System.out.println(pageInfo.getNavigatePages());\n System.out.println(pageInfo.getNextPage());\n System.out.println(pageInfo.getPageNum());\n System.out.println(pageInfo.getPages());\n System.out.println(pageInfo.getPageSize());\n System.out.println(pageInfo.getPrePage());\n System.out.println(pageInfo.getSize());\n System.out.println(pageInfo.getStartRow());\n }","source":"_posts/20170616_Mybatis.md","raw":"---\ntitle: Mybatis入门学习\n\ncomments: true \n\ntags: \n - Mybatis\n\ncategories: \n - 框架相关\n\ndescription: \n\ndate: 2017-06-16\n \n---\n## 什么是 MyBatis ?\n> 官方文档: http://www.mybatis.org/mybatis-3/zh/index.html\n\n> MyBatis 一个基于Java的持久层框架; 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将代理接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。\n\n###### *与hibernate不同的是: mybatis是通过xml映射文件实现代理接口来实现操作数据库的功能*\n\n\n\n### 基本的步骤:\n#### 1. 引入dependency\n\n \n org.mybatis\n mybatis\n 3.4.1\n \n\n\n---\n#### 2. mybatis核心配置文件 #在resources目录下的 mybatis/conf.xml\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n*如果需要(或者测试没日志)可以引入log4j的包和配置文件 方便测试*\n---\n#### 3. 新建实体类--MyUser.java 对应数据库中表my_user \n\n //三个属性\n private int id;\n private String name;\n private int age;\n\n\n---\n#### 4. 新建Mapper映射文件--MyUserMapper.xml(要和实体放到一个目录下)\n\n \n \n \n \n \n \n\n\n*maven项目运行时找不到映射文件:Could not find resource;(原因是maven在构建的时候不会识别src下的配置文件,见生成的class目录)所以有两种方法解决*\n\n 1.在resources下新建目录,目录结构和java下的一致(因为需要保证映射文件和实体在同一个目录下),到时候生成的.class就会和配置文件放到一起,就可以找到了\n 2.(推荐), 添加设置资源目录: 在pom的build下加入:\n \n \n src/main/java\n \n **/*.xml\n \n false\n \n \n\n---\n#### 5.可以测试啦: 添加测试类--MainTest.java\n\n\n private SqlSession util(){\n //配置文件\n String resource = \"mybatis/conf.xml\";\n \n //加载配置mybatis文件\n InputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\n // Reader reader = Resources.getResourceAsReader(resource); //也可以使用这个加载配置\n //构建sqlSession工厂\n SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\n //得到能执行映射文件中sql语句的sqlSession(等同于jdbc中的PreparedStatement)\n SqlSession session = sqlSessionFactory.openSession(true);//设置自动提交事务\n \n return session;\n }\n \n @Test\n public void testSelect() {\n SqlSession session = util();\n //映射sql的标识符字符串(映射文件全类名 + 映射节点id)\n String statement = \"com.mybatis.test1.pojo.MyUserMapper.getUser\";\n //执行sql语句返回结果\n MyUser user = session.selectOne(statement, 12);//两个参数 statement和占位符要填写的参数\n \n System.out.println(user);\n }\n\n---\n\n#### 6. 大功告成 ! \n---\n#### 7. 其他操作 : CURD\n\n> 增删改查实现方法, 1. 增加映射文件内容; \n\n\n \n \n INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})\n \n \n \n \n DELETE FROM my_user\n WHERE id = #{id}\n \n \n \n \n UPDATE my_user\n SET name = #{name}, age = #{age}\n WHERE id = #{id}\n \n \n \n \n \n \n\n\n\n> 2.调用sqlSession的各种方法(方法名基本上是个人都能看出来干嘛的,你就直接试); 比如\n\n\n int result = session.delete(statement, 1);\n \n List users = session.selectList(statement);\n \n ....\n\n---\n#### 8. 基于接口的写法: \n\n> 基于接口有两种具体实现 1.基于注解:不需要自己写实现类,实现类自己\"生成\"; 2:基于xml文件,需要把xml文件和接口放在同一个目录下\n\n\n##### 基于注解\n1.新建一个代理接口--MyUserMapper.java\n\n public interface MyUserMapper {\n \n @Insert(\"INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})\")\n public int add(MyUser user);\n \n @Delete(\"DELETE FROM my_user WHERE id = #{id}\")\n public int del(int id);\n \n @Update(\"UPDATE my_user SET name = #{name}, age = #{age} WHERE id = #{id}\")\n public int edit(MyUser user);\n \n @Select(\"SELECT * FROM my_user WHERE id = #{id}\")\n public MyUser find(int id);\n \n @Select(\"SELECT * FROM my_user\")\n public List getAll();\n }\n\n\n2.\"注册\"到mybatis配置文件--在conf.xml中mappers节点下添加\n\n \n\n\n3.测试\n\n String resource = \"mybatis/conf.xml\";\n InputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\n SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\n SqlSession session = sqlSessionFactory.openSession(true);\n //获取接口动态产生的实现类 再调用方法\n MyUserMapper myUserMapper = session.getMapper(MyUserMapper.class);\n MyUser user = myUserMapper.find(12);\n \n System.out.println(user);\n \n\n---\n\n\n##### 基于配置文件\n1. 和实现xml文件--MyUserMapper.java & MyUserMapper.xml\n\n\n public interface MyUserMapper {\n \n //方法要求 类名必须与.xml名相同; 方法名必须与MyUserMapper.xml中对应的id相同; 并且参数要一一对应\n public List getAll();\n }\n \n \n\n\n2. \"注册\"到mybatis配置文件--在conf.xml中mappers节点下添加(二选一, 只要有一个就可定位Mapper)\n\n\n \n \n \n\n\n3. 测试(方法不变 只需改方法)\n\n---\n#### 9.优化\n\n> 1.数据库文件: 把数据库信息配置到一个文件: db.properties,然后在conf.xml中引入,调用使用EL表达式\n\n\n \n\n\n\n\n> 2.配置别名: 在映射文件中写全类名很长很麻烦,可以在conf.xml中配置别名 alias为别名; 则可以在映射xml文件中写别名表示此类\n\n\n \n \n \n \n\n> 2.配置别名2: 为整个包下类取别名 则别名为此类类名(比如: MyUser)\n\n\n \n \n \n \n---\n\n\n#### 10.对于数据表字段名和实体属性名不一致的问题\n\n\n> 当表字段名和实体属性名不同 就会无法获取数据(区分大小写), 对应的属性即为null(相关类型默认值) 原因是查到的数据无法映射到对应的result实体上,所以只要创建一个映射关系就能解决这个问题\n\n\n1. 方法一: 指定字段别名(sql语句的方法, 直接指定字段别名为实体属性名)\n\n\n \n\n\n\n2. 方法二: mybatis提供resultMap用于结果映射; like this\n\n\n \n \n \n \n \n \n \n \n \n\n\n---\n\n#### 11.一对一和一对多的实现\n\n> 对于涉及到多表查询, 一般有两种方式: 1.一个表一个表查询,用第一个表查到的数据组成第二个查询语句(也叫嵌套查询); 2.sql关联查询,一条语句,一次查询,语句比较复杂(也叫嵌套结果);\n\n>为了尽可能的减少代码量(当然,去掉不必要的\"体积\"的麻烦),而且效率上 嵌套结果>存储过程>嵌套查询;\n\n##### 嵌套结果示例:\n\n1. 一对一\n\n> 两个实体类以及一个结果封装类(由于不能仅仅用一个实体接收查询的所有的字段, so其用于封装查询的结果)\n\n // 对应数据库中表: order (字段有所不同,参考查询语句)\n public class Order {\n private int id;\n private String orderNo;\n private float orderPrice;\n private User user;\n \n ... //构造方法, setter, getter, toString等方法\n }\n \n // 对应数据库中表: user\n public class User {\n private int id;\n private String name;\n private int age;\n \n ...\n }\n \n // 结果封装类 id为user的id, 属性包含其他两个实体类(由于是一对一,则参数也可以把实体的参数复制过来,那查询mapper中resultMap有所不同)\n public class UserOrder {\n private String id;\n private User user;\n private Order order;\n \n ...\n }\n\n\n\n> 映射文件中查询的编写:\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n> 测试类:\n\n\n ...\n UserOrder order = orderMapper.getOrderInfo(12);\n System.out.println(order);\n\n\n1. 一对多\n\n> 一的一方同上,多的一方就需要一个新的类封装实体对象的集合,并且需要修改mapper写法\n\n\n // 实体类不变 结果封装类为:\n public class UserOrder2 {\n private String id;\n private User user;\n private List orders;\n }\n\n\n> 映射文件:\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n*高级结果映射详细简介见: http://zj2626.github.io/2017/06/19/20170619_Mybatis/*\n\n> 测试类\n\n UserOrder2 order2 = orderMapper.getOrderInfo2(12);\n System.out.println(order2);\n\n\n#### 12.一级缓存与二级缓存\n\n> 与hibernate相似, mybatis也存在缓存并且默认开启一级缓存,mybatis一级缓存是session级别的,而二级缓存是namespace(statement)级别的(即每个mapper文件就是一个二级缓存范围,需要配置)\n\n> 配置二级缓存\n\n \n \n or\n \n \n\n#### 13.与spring集成\n\n> 引入依赖\n\n \n org.mybatis\n mybatis-spring\n 1.3.0\n \n \n> 配置mapper 同上\n\n> 配置spring配置文件:\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n> 配置mybatis配置文件 里面没有配置内容(但是必须要)\n\n\n \n \n \n \n \n\n#### n.分页插件 --- PageHelper: 一个分页插件,支持多种数据库,原理大概是在执行sql语句之前(拦截器)进行了操作改写了sql语句,实现分页\n\n1. 导入依赖\n\n\n \n com.github.pagehelper\n pagehelper\n \n \n2. 配置插件--拦截器(在mybatis的配置文件中), 笔者在spring中集成了mybatis的配置\n\n\n \n \n \n \n \n \n \n \n3. 测试分页\n\n\n @Test\n public void testPage() {\n //初始化Spring容器\n ApplicationContext applicationContext = new ClassPathXmlApplicationContext(\"classpath:spring/applicationContext-dao.xml\");\n \n //获取代理对象\n TbItemMapper itemMapper = applicationContext.getBean(TbItemMapper.class);\n \n //执行sql语句前设置分页信息使用PageHelper的startPage方法\n PageHelper.startPage(1, 10);\n \n //查询\n TbItemExample example = new TbItemExample();\n List list = itemMapper.selectByExample(example);\n \n //取分页信息PageInfo 总记录数 总页数 当前页\n PageInfo pageInfo = new PageInfo<>(list);\n System.out.println(pageInfo.getTotal());\n System.out.println(pageInfo.getEndRow());\n System.out.println(pageInfo.getFirstPage());\n System.out.println(pageInfo.getLastPage());\n System.out.println(pageInfo.getList());\n System.out.println(pageInfo.getNavigatePages());\n System.out.println(pageInfo.getNextPage());\n System.out.println(pageInfo.getPageNum());\n System.out.println(pageInfo.getPages());\n System.out.println(pageInfo.getPageSize());\n System.out.println(pageInfo.getPrePage());\n System.out.println(pageInfo.getSize());\n System.out.println(pageInfo.getStartRow());\n }","slug":"20170616_Mybatis","published":1,"updated":"2021-03-10T13:50:15.236Z","layout":"post","photos":[],"link":"","_id":"ckm3invgn009224ujh4ok12d6","content":"

什么是 MyBatis ?

\n

官方文档: http://www.mybatis.org/mybatis-3/zh/index.html

\n
\n
\n

MyBatis 一个基于Java的持久层框架; 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将代理接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

\n
\n
与hibernate不同的是: mybatis是通过xml映射文件实现代理接口来实现操作数据库的功能
\n

基本的步骤:

1. 引入dependency

<dependency>\n    <groupId>org.mybatis</groupId>\n    <artifactId>mybatis</artifactId>\n    <version>3.4.1</version>\n</dependency>\n

\n

2. mybatis核心配置文件 #在resources目录下的 mybatis/conf.xml

<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">\n<configuration>\n    <!--可以读取数据库配置文件 用EL表达式获取参数-->\n    <!--<properties resource="classpath:mybatis/db.properties"/>-->\n\n    <!--\n        development : 开发模式\n        work : 工作模式\n     -->\n    <environments default="development">\n        <environment id="development">\n            <transactionManager type="JDBC"/>\n            <dataSource type="POOLED">\n                <property name="driver" value="com.mysql.jdbc.Driver"/>\n                <property name="url"\n                          value="jdbc:mysql://localhost:3306/test?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>\n                <property name="username" value="root"/>\n                <property name="password" value="fangshuoit"/>\n            </dataSource>\n        </environment>\n    </environments>\n\n    <!--映射文件所在位置 不能使用通配符(和spring整合时候可以使用)-->\n    <mappers>\n        <mapper resource="com/mybatis/test1/pojo/MyUserMapper.xml"/>\n    </mappers>\n</configuration>\n

如果需要(或者测试没日志)可以引入log4j的包和配置文件 方便测试

3. 新建实体类–MyUser.java 对应数据库中表my_user

//三个属性\nprivate int id;\nprivate String name;\nprivate int age;\n

\n

4. 新建Mapper映射文件–MyUserMapper.xml(要和实体放到一个目录下)

<?xml version="1.0" encoding="UTF-8" ?>\n<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">\n<mapper namespace="com.mybatis.test1.pojo.MyUserMapper">\n    <!--\n        根据id查询得到一个user对象 其中sql语句中的表名,查询参数名,where语句中 键都是数据库中表的写法, 要传入的参数占位符是书体属性名\n     -->\n    <select id="getUser" parameterType="int" resultType="com.mybatis.test1.pojo.MyUser">\n        SELECT *\n        FROM my_user\n        WHERE id = #{id}\n    </select>\n</mapper>\n

maven项目运行时找不到映射文件:Could not find resource;(原因是maven在构建的时候不会识别src下的配置文件,见生成的class目录)所以有两种方法解决

\n
1.在resources下新建目录,目录结构和java下的一致(因为需要保证映射文件和实体在同一个目录下),到时候生成的.class就会和配置文件放到一起,就可以找到了\n2.(推荐), 添加设置资源目录: 在pom的build下加入:\n            <resources>\n                <resource>\n                    <directory>src/main/java</directory>\n                    <includes>\n                        <include>**/*.xml</include>\n                    </includes>\n                    <filtering>false</filtering>\n                </resource>\n            </resources>\n

\n

5.可以测试啦: 添加测试类–MainTest.java

    private SqlSession util(){\n        //配置文件\n        String resource = "mybatis/conf.xml";\n\n        //加载配置mybatis文件\n        InputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\n//      Reader reader = Resources.getResourceAsReader(resource); //也可以使用这个加载配置\n        //构建sqlSession工厂\n        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\n        //得到能执行映射文件中sql语句的sqlSession(等同于jdbc中的PreparedStatement)\n        SqlSession session = sqlSessionFactory.openSession(true);//设置自动提交事务\n\n        return session;\n    }\n\n    @Test\n    public void testSelect() {\n        SqlSession session = util();\n        //映射sql的标识符字符串(映射文件全类名 + 映射节点id)\n        String statement = "com.mybatis.test1.pojo.MyUserMapper.getUser";\n        //执行sql语句返回结果\n        MyUser user = session.selectOne(statement, 12);//两个参数 statement和占位符要填写的参数\n\n        System.out.println(user);\n    }\n

\n

6. 大功告成 !


\n

7. 其他操作 : CURD

\n

增删改查实现方法, 1. 增加映射文件内容;

\n
\n
<!--\n        插入一个用户\n    -->\n    <insert id="addUser" parameterType="com.mybatis.test1.pojo.MyUser">\n        INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})\n    </insert>\n\n    <!--\n        根据id删除一个用户\n    -->\n    <delete id="delUser" parameterType="int">\n        DELETE FROM my_user\n        WHERE id = #{id}\n    </delete>\n\n    <!--\n        更新用户信息\n    -->\n    <update id="editUser" parameterType="com.mybatis.test1.pojo.MyUser">\n        UPDATE my_user\n        SET name = #{name}, age = #{age}\n        WHERE id = #{id}\n    </update>\n\n    <!--\n        根据id查询得到一个user对象\n     -->\n    <select id="getUser" parameterType="int" resultType="com.mybatis.test1.pojo.MyUser">\n        SELECT *\n        FROM my_user\n        WHERE id = #{id}\n    </select>\n\n    <select id="getAllUser" resultType="com.mybatis.test1.pojo.MyUser">\n        SELECT *\n        FROM my_user\n    </select>\n
\n

2.调用sqlSession的各种方法(方法名基本上是个人都能看出来干嘛的,你就直接试); 比如

\n
\n
int result = session.delete(statement, 1);\n\nList<MyUser> users = session.selectList(statement);\n\n....\n

\n

8. 基于接口的写法:

\n

基于接口有两种具体实现 1.基于注解:不需要自己写实现类,实现类自己”生成”; 2:基于xml文件,需要把xml文件和接口放在同一个目录下

\n
\n
基于注解

1.新建一个代理接口–MyUserMapper.java

\n
public interface MyUserMapper {\n\n    @Insert("INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})")\n    public int add(MyUser user);\n\n    @Delete("DELETE FROM my_user WHERE id = #{id}")\n    public int del(int id);\n\n    @Update("UPDATE my_user SET name = #{name}, age = #{age} WHERE id = #{id}")\n    public int edit(MyUser user);\n\n    @Select("SELECT * FROM my_user WHERE id = #{id}")\n    public MyUser find(int id);\n\n    @Select("SELECT * FROM my_user")\n    public List<MyUser> getAll();\n}\n

2.”注册”到mybatis配置文件–在conf.xml中mappers节点下添加

\n
<mapper class="com.mybatis.test2.pojo.MyUserMapper"/>\n

3.测试

\n
String resource = "mybatis/conf.xml";\nInputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\nSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\nSqlSession session = sqlSessionFactory.openSession(true);\n//获取接口动态产生的实现类 再调用方法\nMyUserMapper myUserMapper = session.getMapper(MyUserMapper.class);\nMyUser user = myUserMapper.find(12);\n\nSystem.out.println(user);\n

\n
基于配置文件
    \n
  1. 和实现xml文件–MyUserMapper.java & MyUserMapper.xml
  2. \n
\n
public interface MyUserMapper {\n\n    //方法要求 类名必须与.xml名相同; 方法名必须与MyUserMapper.xml中对应的id相同; 并且参数要一一对应\n    public List<MyUser> getAll();\n}\n\n<select id="getAllUser" resultType="com.mybatis.test2.pojo.MyUser">\n    SELECT *\n    FROM my_user\n</select>\n
    \n
  1. “注册”到mybatis配置文件–在conf.xml中mappers节点下添加(二选一, 只要有一个就可定位Mapper)
  2. \n
\n
<mapper class="com.mybatis.test2.pojo.MyUserMapper"/>\n\n<mapper resource="com/mybatis/test2/pojo/MyUserMapper.xml"/>\n
    \n
  1. 测试(方法不变 只需改方法)
  2. \n
\n
\n

9.优化

\n

1.数据库文件: 把数据库信息配置到一个文件: db.properties,然后在conf.xml中引入,调用使用EL表达式

\n
\n
<properties resource="classpath:mybatis/db.properties"/>\n
\n

2.配置别名: 在映射文件中写全类名很长很麻烦,可以在conf.xml中配置别名 alias为别名; 则可以在映射xml文件中写别名表示此类

\n
\n
<typeAliases>\n    <typeAlias type="com.mybatis.test2.pojo.MyUser" alias="_MyUser"/>\n</typeAliases>\n
\n

2.配置别名2: 为整个包下类取别名 则别名为此类类名(比如: MyUser)

\n
\n
<typeAliases>\n    <package name="com.mybatis.test2.pojo"/>\n</typeAliases>\n

\n

10.对于数据表字段名和实体属性名不一致的问题

\n

当表字段名和实体属性名不同 就会无法获取数据(区分大小写), 对应的属性即为null(相关类型默认值) 原因是查到的数据无法映射到对应的result实体上,所以只要创建一个映射关系就能解决这个问题

\n
\n
    \n
  1. 方法一: 指定字段别名(sql语句的方法, 直接指定字段别名为实体属性名)
  2. \n
\n
<select id="getOrder" parameterType="int" resultType="Order">\n    SELECT order_id id, order_no orderNo, order_price orderPrice\n    FROM `order`\n    WHERE order_id = #{id}\n</select>\n
    \n
  1. 方法二: mybatis提供resultMap用于结果映射; like this
  2. \n
\n
<!--\n        type: 映射实体类型 id主键 property实体属性名  column字段名\n    -->\n\n<resultMap id="order" type="Order">\n    <id property="id" column="order_id"/>\n    <result property="orderNo" column="order_no"/>\n    <result property="orderPrice" column="order_price"/>\n</resultMap>\n\n<select id="getOrder" parameterType="int" resultMap="order">\n    SELECT *\n    FROM `order`\n    WHERE order_id = #{id}\n</select>\n

\n

11.一对一和一对多的实现

\n

对于涉及到多表查询, 一般有两种方式: 1.一个表一个表查询,用第一个表查到的数据组成第二个查询语句(也叫嵌套查询); 2.sql关联查询,一条语句,一次查询,语句比较复杂(也叫嵌套结果);

\n
\n
\n

为了尽可能的减少代码量(当然,去掉不必要的”体积”的麻烦),而且效率上 嵌套结果>存储过程>嵌套查询;

\n
\n
嵌套结果示例:
    \n
  1. 一对一
  2. \n
\n
\n

两个实体类以及一个结果封装类(由于不能仅仅用一个实体接收查询的所有的字段, so其用于封装查询的结果)

\n
\n
// 对应数据库中表: order (字段有所不同,参考查询语句)\npublic class Order {\n    private int id;\n    private String orderNo;\n    private float orderPrice;\n    private User user;\n\n    ... //构造方法, setter, getter, toString等方法\n}\n\n// 对应数据库中表: user\npublic class User {\n    private int id;\n    private String name;\n    private int age;\n\n    ...\n}\n\n// 结果封装类 id为user的id, 属性包含其他两个实体类(由于是一对一,则参数也可以把实体的参数复制过来,那查询mapper中resultMap有所不同)\npublic class UserOrder {\n    private String id;\n    private User user;\n    private Order order;\n\n    ...\n}\n
\n

映射文件中查询的编写:

\n
\n
<!-使用resultMap封装查询到的所有数据-->\n\n<select id="getOrderInfo" parameterType="int" resultMap="uo">\n    SELECT *\n    FROM user u, `order` o\n    WHERE u.id = #{id} AND u.id = o.user_id\n</select>\n\n<!-column是查询输出结果的字段名, 如果查询的表之间没有同名字段则column是字段名,如果有字段冲突,则会有所变化(一般是"表别名_字段名"),以防万一要多测试-->\n<!-property是实体属性名-->\n<!-association:复杂类型联合,把多个字段映射联合映射为一个对象或其他 需要书写javaType表示要映射的类型 property表示映射类型的属性名-->\n<resultMap id="uo" type="UserOrder">\n    <id property="id" column="id"/>\n    <association property="user" javaType="User">\n        <id property="id" column="id"/>\n        <result property="name" column="name"/>\n        <result property="age" column="age"/>\n    </association>\n    <association property="order" javaType="Order">\n        <id property="id" column="order_id"/>\n        <result property="orderNo" column="order_no"/>\n        <result property="orderPrice" column="order_price"/>\n    </association>\n</resultMap>\n\n<!-记得书写代理接口-->\n
\n

测试类:

\n
\n
...\nUserOrder order = orderMapper.getOrderInfo(12);\nSystem.out.println(order);\n
    \n
  1. 一对多
  2. \n
\n
\n

一的一方同上,多的一方就需要一个新的类封装实体对象的集合,并且需要修改mapper写法

\n
\n
// 实体类不变 结果封装类为:\npublic class UserOrder2 {\n    private String id;\n    private User user;\n    private List<Order> orders;\n}\n
\n

映射文件:

\n
\n
<!-如果你在mysql中输入sql语句,查看结果就会发现:user只有一种但是每条数据的字段数据都有并且相同,order的字段数据每条都不一样-->\n<!-collection:复杂类型集合,-->\n<select id="getOrderInfo2" parameterType="int" resultMap="uo2">\n    SELECT *\n    FROM user u, `order` o\n    WHERE u.id = #{id} AND u.id = o.user_id\n</select>\n\n<!--collection: 封装字段为集合类型 property: 类中的属性名 内容是集合数据的类型的属性-->\n<!--oftype: 集合中元素对象类型-->\n<resultMap id="uo2" type="UserOrder2">\n    <id property="id" column="id"/>\n    <association property="user" javaType="User">\n        <id property="id" column="id"/>\n        <result property="name" column="name"/>\n        <result property="age" column="age"/>\n    </association>\n    <collection property="orders" ofType="Order">\n        <id property="id" column="order_id"/>\n        <result property="orderNo" column="order_no"/>\n        <result property="orderPrice" column="order_price"/>\n    </collection>\n</resultMap>\n

高级结果映射详细简介见: http://zj2626.github.io/2017/06/19/20170619_Mybatis/

\n
\n

测试类

\n
\n
UserOrder2 order2 = orderMapper.getOrderInfo2(12);\nSystem.out.println(order2);\n

12.一级缓存与二级缓存

\n

与hibernate相似, mybatis也存在缓存并且默认开启一级缓存,mybatis一级缓存是session级别的,而二级缓存是namespace(statement)级别的(即每个mapper文件就是一个二级缓存范围,需要配置)

\n
\n
\n

配置二级缓存

\n
\n
<cache/>\n\nor\n\n<cache eviction="LRU" flushInterval="60000" size="512"/>\n

13.与spring集成

\n

引入依赖

\n
\n
<dependency>\n    <groupId>org.mybatis</groupId>\n    <artifactId>mybatis-spring</artifactId>\n    <version>1.3.0</version>\n</dependency>\n
\n

配置mapper 同上

\n
\n
\n

配置spring配置文件:

\n
\n
<?xml version="1.0" encoding="UTF-8"?>\n<beans xmlns="http://www.springframework.org/schema/beans" \n    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n    xmlns:p="http://www.springframework.org/schema/p" \n    xmlns:context="http://www.springframework.org/schema/context"\n    xmlns:tx="http://www.springframework.org/schema/tx"\n    xsi:schemaLocation="\n        http://www.springframework.org/schema/beans\n        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd\n        http://www.springframework.org/schema/context\n        http://www.springframework.org/schema/context/spring-context-3.2.xsd\n        http://www.springframework.org/schema/tx\n        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">\n    <!-- 1. 数据源 : DriverManagerDataSource -->\n    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">\n        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>\n        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>\n        <property name="username" value="root"/>\n        <property name="password" value="root"/>\n    </bean>\n\n    <!-- \n        2. mybatis的SqlSession的工厂: SqlSessionFactoryBean \n            dataSource / typeAliasesPackage\n    -->\n    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">\n        <property name="dataSource" ref="datasource"/>\n        <property name="typeAliasesPackage" value="com/mybatis/test3/bean"/>\n    </bean>\n\n    <!-- \n        3. mybatis自动扫描加载Sql映射文件(即接口) : MapperScannerConfigurer \n            sqlSessionFactory / basePackage\n    -->\n    <bean id="config" class="org.mybatis.spring.mapper.MapperScannerConfigurer">\n        <property name="basePackage" value="com/mybatis/test3/mapper"/>\n        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>\n    </bean>\n\n    <!-- 4. 事务管理 : DataSourceTransactionManager -->\n    <bean id="manager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">\n        <property name="dataSource" ref="datasource"/>\n    </bean>\n\n    <!-- 5. 使用声明式事务 -->\n    <tx:annotation-driven transaction-manager="manager" />\n</beans>\n
\n

配置mybatis配置文件 里面没有配置内容(但是必须要)

\n
\n
<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">\n<configuration>\n\n</configuration>\n

n.分页插件 — PageHelper: 一个分页插件,支持多种数据库,原理大概是在执行sql语句之前(拦截器)进行了操作改写了sql语句,实现分页

    \n
  1. 导入依赖
  2. \n
\n
<dependency>\n    <groupId>com.github.pagehelper</groupId>\n    <artifactId>pagehelper</artifactId>\n</dependency>\n
    \n
  1. 配置插件–拦截器(在mybatis的配置文件中), 笔者在spring中集成了mybatis的配置
  2. \n
\n
<plugins>\n    <!--配置PageHelper插件-->\n    <plugin interceptor="com.github.pagehelper.PageHelper">\n        <!--配置方言(数据库识别)-->\n        <property name="dialect" value="mysql"/>\n    </plugin>\n</plugins>\n
    \n
  1. 测试分页
  2. \n
\n
@Test\npublic void testPage() {\n    //初始化Spring容器\n    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-dao.xml");\n\n    //获取代理对象\n    TbItemMapper itemMapper = applicationContext.getBean(TbItemMapper.class);\n\n    //执行sql语句前设置分页信息使用PageHelper的startPage方法\n    PageHelper.startPage(1, 10);\n\n    //查询\n    TbItemExample example = new TbItemExample();\n    List<TbItem> list = itemMapper.selectByExample(example);\n\n    //取分页信息PageInfo 总记录数 总页数 当前页\n    PageInfo<TbItem> pageInfo = new PageInfo<>(list);\n    System.out.println(pageInfo.getTotal());\n    System.out.println(pageInfo.getEndRow());\n    System.out.println(pageInfo.getFirstPage());\n    System.out.println(pageInfo.getLastPage());\n    System.out.println(pageInfo.getList());\n    System.out.println(pageInfo.getNavigatePages());\n    System.out.println(pageInfo.getNextPage());\n    System.out.println(pageInfo.getPageNum());\n    System.out.println(pageInfo.getPages());\n    System.out.println(pageInfo.getPageSize());\n    System.out.println(pageInfo.getPrePage());\n    System.out.println(pageInfo.getSize());\n    System.out.println(pageInfo.getStartRow());\n}\n
","site":{"data":{}},"excerpt":"

什么是 MyBatis ?

\n

官方文档: http://www.mybatis.org/mybatis-3/zh/index.html

\n
\n
\n

MyBatis 一个基于Java的持久层框架; 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将代理接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

\n
\n
与hibernate不同的是: mybatis是通过xml映射文件实现代理接口来实现操作数据库的功能
","more":"

基本的步骤:

1. 引入dependency

<dependency>\n    <groupId>org.mybatis</groupId>\n    <artifactId>mybatis</artifactId>\n    <version>3.4.1</version>\n</dependency>\n

\n

2. mybatis核心配置文件 #在resources目录下的 mybatis/conf.xml

<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">\n<configuration>\n    <!--可以读取数据库配置文件 用EL表达式获取参数-->\n    <!--<properties resource="classpath:mybatis/db.properties"/>-->\n\n    <!--\n        development : 开发模式\n        work : 工作模式\n     -->\n    <environments default="development">\n        <environment id="development">\n            <transactionManager type="JDBC"/>\n            <dataSource type="POOLED">\n                <property name="driver" value="com.mysql.jdbc.Driver"/>\n                <property name="url"\n                          value="jdbc:mysql://localhost:3306/test?zeroDateTimeBehavior=convertToNull&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>\n                <property name="username" value="root"/>\n                <property name="password" value="fangshuoit"/>\n            </dataSource>\n        </environment>\n    </environments>\n\n    <!--映射文件所在位置 不能使用通配符(和spring整合时候可以使用)-->\n    <mappers>\n        <mapper resource="com/mybatis/test1/pojo/MyUserMapper.xml"/>\n    </mappers>\n</configuration>\n

如果需要(或者测试没日志)可以引入log4j的包和配置文件 方便测试

3. 新建实体类–MyUser.java 对应数据库中表my_user

//三个属性\nprivate int id;\nprivate String name;\nprivate int age;\n

\n

4. 新建Mapper映射文件–MyUserMapper.xml(要和实体放到一个目录下)

<?xml version="1.0" encoding="UTF-8" ?>\n<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">\n<mapper namespace="com.mybatis.test1.pojo.MyUserMapper">\n    <!--\n        根据id查询得到一个user对象 其中sql语句中的表名,查询参数名,where语句中 键都是数据库中表的写法, 要传入的参数占位符是书体属性名\n     -->\n    <select id="getUser" parameterType="int" resultType="com.mybatis.test1.pojo.MyUser">\n        SELECT *\n        FROM my_user\n        WHERE id = #{id}\n    </select>\n</mapper>\n

maven项目运行时找不到映射文件:Could not find resource;(原因是maven在构建的时候不会识别src下的配置文件,见生成的class目录)所以有两种方法解决

\n
1.在resources下新建目录,目录结构和java下的一致(因为需要保证映射文件和实体在同一个目录下),到时候生成的.class就会和配置文件放到一起,就可以找到了\n2.(推荐), 添加设置资源目录: 在pom的build下加入:\n            <resources>\n                <resource>\n                    <directory>src/main/java</directory>\n                    <includes>\n                        <include>**/*.xml</include>\n                    </includes>\n                    <filtering>false</filtering>\n                </resource>\n            </resources>\n

\n

5.可以测试啦: 添加测试类–MainTest.java

    private SqlSession util(){\n        //配置文件\n        String resource = "mybatis/conf.xml";\n\n        //加载配置mybatis文件\n        InputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\n//      Reader reader = Resources.getResourceAsReader(resource); //也可以使用这个加载配置\n        //构建sqlSession工厂\n        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\n        //得到能执行映射文件中sql语句的sqlSession(等同于jdbc中的PreparedStatement)\n        SqlSession session = sqlSessionFactory.openSession(true);//设置自动提交事务\n\n        return session;\n    }\n\n    @Test\n    public void testSelect() {\n        SqlSession session = util();\n        //映射sql的标识符字符串(映射文件全类名 + 映射节点id)\n        String statement = "com.mybatis.test1.pojo.MyUserMapper.getUser";\n        //执行sql语句返回结果\n        MyUser user = session.selectOne(statement, 12);//两个参数 statement和占位符要填写的参数\n\n        System.out.println(user);\n    }\n

\n

6. 大功告成 !


\n

7. 其他操作 : CURD

\n

增删改查实现方法, 1. 增加映射文件内容;

\n
\n
<!--\n        插入一个用户\n    -->\n    <insert id="addUser" parameterType="com.mybatis.test1.pojo.MyUser">\n        INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})\n    </insert>\n\n    <!--\n        根据id删除一个用户\n    -->\n    <delete id="delUser" parameterType="int">\n        DELETE FROM my_user\n        WHERE id = #{id}\n    </delete>\n\n    <!--\n        更新用户信息\n    -->\n    <update id="editUser" parameterType="com.mybatis.test1.pojo.MyUser">\n        UPDATE my_user\n        SET name = #{name}, age = #{age}\n        WHERE id = #{id}\n    </update>\n\n    <!--\n        根据id查询得到一个user对象\n     -->\n    <select id="getUser" parameterType="int" resultType="com.mybatis.test1.pojo.MyUser">\n        SELECT *\n        FROM my_user\n        WHERE id = #{id}\n    </select>\n\n    <select id="getAllUser" resultType="com.mybatis.test1.pojo.MyUser">\n        SELECT *\n        FROM my_user\n    </select>\n
\n

2.调用sqlSession的各种方法(方法名基本上是个人都能看出来干嘛的,你就直接试); 比如

\n
\n
int result = session.delete(statement, 1);\n\nList<MyUser> users = session.selectList(statement);\n\n....\n

\n

8. 基于接口的写法:

\n

基于接口有两种具体实现 1.基于注解:不需要自己写实现类,实现类自己”生成”; 2:基于xml文件,需要把xml文件和接口放在同一个目录下

\n
\n
基于注解

1.新建一个代理接口–MyUserMapper.java

\n
public interface MyUserMapper {\n\n    @Insert("INSERT INTO my_user (id, name, age) VALUES (#{id}, #{name}, #{age})")\n    public int add(MyUser user);\n\n    @Delete("DELETE FROM my_user WHERE id = #{id}")\n    public int del(int id);\n\n    @Update("UPDATE my_user SET name = #{name}, age = #{age} WHERE id = #{id}")\n    public int edit(MyUser user);\n\n    @Select("SELECT * FROM my_user WHERE id = #{id}")\n    public MyUser find(int id);\n\n    @Select("SELECT * FROM my_user")\n    public List<MyUser> getAll();\n}\n

2.”注册”到mybatis配置文件–在conf.xml中mappers节点下添加

\n
<mapper class="com.mybatis.test2.pojo.MyUserMapper"/>\n

3.测试

\n
String resource = "mybatis/conf.xml";\nInputStream input = MainTest.class.getClassLoader().getResourceAsStream(resource);\nSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(input);\nSqlSession session = sqlSessionFactory.openSession(true);\n//获取接口动态产生的实现类 再调用方法\nMyUserMapper myUserMapper = session.getMapper(MyUserMapper.class);\nMyUser user = myUserMapper.find(12);\n\nSystem.out.println(user);\n

\n
基于配置文件
    \n
  1. 和实现xml文件–MyUserMapper.java & MyUserMapper.xml
  2. \n
\n
public interface MyUserMapper {\n\n    //方法要求 类名必须与.xml名相同; 方法名必须与MyUserMapper.xml中对应的id相同; 并且参数要一一对应\n    public List<MyUser> getAll();\n}\n\n<select id="getAllUser" resultType="com.mybatis.test2.pojo.MyUser">\n    SELECT *\n    FROM my_user\n</select>\n
    \n
  1. “注册”到mybatis配置文件–在conf.xml中mappers节点下添加(二选一, 只要有一个就可定位Mapper)
  2. \n
\n
<mapper class="com.mybatis.test2.pojo.MyUserMapper"/>\n\n<mapper resource="com/mybatis/test2/pojo/MyUserMapper.xml"/>\n
    \n
  1. 测试(方法不变 只需改方法)
  2. \n
\n
\n

9.优化

\n

1.数据库文件: 把数据库信息配置到一个文件: db.properties,然后在conf.xml中引入,调用使用EL表达式

\n
\n
<properties resource="classpath:mybatis/db.properties"/>\n
\n

2.配置别名: 在映射文件中写全类名很长很麻烦,可以在conf.xml中配置别名 alias为别名; 则可以在映射xml文件中写别名表示此类

\n
\n
<typeAliases>\n    <typeAlias type="com.mybatis.test2.pojo.MyUser" alias="_MyUser"/>\n</typeAliases>\n
\n

2.配置别名2: 为整个包下类取别名 则别名为此类类名(比如: MyUser)

\n
\n
<typeAliases>\n    <package name="com.mybatis.test2.pojo"/>\n</typeAliases>\n

\n

10.对于数据表字段名和实体属性名不一致的问题

\n

当表字段名和实体属性名不同 就会无法获取数据(区分大小写), 对应的属性即为null(相关类型默认值) 原因是查到的数据无法映射到对应的result实体上,所以只要创建一个映射关系就能解决这个问题

\n
\n
    \n
  1. 方法一: 指定字段别名(sql语句的方法, 直接指定字段别名为实体属性名)
  2. \n
\n
<select id="getOrder" parameterType="int" resultType="Order">\n    SELECT order_id id, order_no orderNo, order_price orderPrice\n    FROM `order`\n    WHERE order_id = #{id}\n</select>\n
    \n
  1. 方法二: mybatis提供resultMap用于结果映射; like this
  2. \n
\n
<!--\n        type: 映射实体类型 id主键 property实体属性名  column字段名\n    -->\n\n<resultMap id="order" type="Order">\n    <id property="id" column="order_id"/>\n    <result property="orderNo" column="order_no"/>\n    <result property="orderPrice" column="order_price"/>\n</resultMap>\n\n<select id="getOrder" parameterType="int" resultMap="order">\n    SELECT *\n    FROM `order`\n    WHERE order_id = #{id}\n</select>\n

\n

11.一对一和一对多的实现

\n

对于涉及到多表查询, 一般有两种方式: 1.一个表一个表查询,用第一个表查到的数据组成第二个查询语句(也叫嵌套查询); 2.sql关联查询,一条语句,一次查询,语句比较复杂(也叫嵌套结果);

\n
\n
\n

为了尽可能的减少代码量(当然,去掉不必要的”体积”的麻烦),而且效率上 嵌套结果>存储过程>嵌套查询;

\n
\n
嵌套结果示例:
    \n
  1. 一对一
  2. \n
\n
\n

两个实体类以及一个结果封装类(由于不能仅仅用一个实体接收查询的所有的字段, so其用于封装查询的结果)

\n
\n
// 对应数据库中表: order (字段有所不同,参考查询语句)\npublic class Order {\n    private int id;\n    private String orderNo;\n    private float orderPrice;\n    private User user;\n\n    ... //构造方法, setter, getter, toString等方法\n}\n\n// 对应数据库中表: user\npublic class User {\n    private int id;\n    private String name;\n    private int age;\n\n    ...\n}\n\n// 结果封装类 id为user的id, 属性包含其他两个实体类(由于是一对一,则参数也可以把实体的参数复制过来,那查询mapper中resultMap有所不同)\npublic class UserOrder {\n    private String id;\n    private User user;\n    private Order order;\n\n    ...\n}\n
\n

映射文件中查询的编写:

\n
\n
<!-使用resultMap封装查询到的所有数据-->\n\n<select id="getOrderInfo" parameterType="int" resultMap="uo">\n    SELECT *\n    FROM user u, `order` o\n    WHERE u.id = #{id} AND u.id = o.user_id\n</select>\n\n<!-column是查询输出结果的字段名, 如果查询的表之间没有同名字段则column是字段名,如果有字段冲突,则会有所变化(一般是"表别名_字段名"),以防万一要多测试-->\n<!-property是实体属性名-->\n<!-association:复杂类型联合,把多个字段映射联合映射为一个对象或其他 需要书写javaType表示要映射的类型 property表示映射类型的属性名-->\n<resultMap id="uo" type="UserOrder">\n    <id property="id" column="id"/>\n    <association property="user" javaType="User">\n        <id property="id" column="id"/>\n        <result property="name" column="name"/>\n        <result property="age" column="age"/>\n    </association>\n    <association property="order" javaType="Order">\n        <id property="id" column="order_id"/>\n        <result property="orderNo" column="order_no"/>\n        <result property="orderPrice" column="order_price"/>\n    </association>\n</resultMap>\n\n<!-记得书写代理接口-->\n
\n

测试类:

\n
\n
...\nUserOrder order = orderMapper.getOrderInfo(12);\nSystem.out.println(order);\n
    \n
  1. 一对多
  2. \n
\n
\n

一的一方同上,多的一方就需要一个新的类封装实体对象的集合,并且需要修改mapper写法

\n
\n
// 实体类不变 结果封装类为:\npublic class UserOrder2 {\n    private String id;\n    private User user;\n    private List<Order> orders;\n}\n
\n

映射文件:

\n
\n
<!-如果你在mysql中输入sql语句,查看结果就会发现:user只有一种但是每条数据的字段数据都有并且相同,order的字段数据每条都不一样-->\n<!-collection:复杂类型集合,-->\n<select id="getOrderInfo2" parameterType="int" resultMap="uo2">\n    SELECT *\n    FROM user u, `order` o\n    WHERE u.id = #{id} AND u.id = o.user_id\n</select>\n\n<!--collection: 封装字段为集合类型 property: 类中的属性名 内容是集合数据的类型的属性-->\n<!--oftype: 集合中元素对象类型-->\n<resultMap id="uo2" type="UserOrder2">\n    <id property="id" column="id"/>\n    <association property="user" javaType="User">\n        <id property="id" column="id"/>\n        <result property="name" column="name"/>\n        <result property="age" column="age"/>\n    </association>\n    <collection property="orders" ofType="Order">\n        <id property="id" column="order_id"/>\n        <result property="orderNo" column="order_no"/>\n        <result property="orderPrice" column="order_price"/>\n    </collection>\n</resultMap>\n

高级结果映射详细简介见: http://zj2626.github.io/2017/06/19/20170619_Mybatis/

\n
\n

测试类

\n
\n
UserOrder2 order2 = orderMapper.getOrderInfo2(12);\nSystem.out.println(order2);\n

12.一级缓存与二级缓存

\n

与hibernate相似, mybatis也存在缓存并且默认开启一级缓存,mybatis一级缓存是session级别的,而二级缓存是namespace(statement)级别的(即每个mapper文件就是一个二级缓存范围,需要配置)

\n
\n
\n

配置二级缓存

\n
\n
<cache/>\n\nor\n\n<cache eviction="LRU" flushInterval="60000" size="512"/>\n

13.与spring集成

\n

引入依赖

\n
\n
<dependency>\n    <groupId>org.mybatis</groupId>\n    <artifactId>mybatis-spring</artifactId>\n    <version>1.3.0</version>\n</dependency>\n
\n

配置mapper 同上

\n
\n
\n

配置spring配置文件:

\n
\n
<?xml version="1.0" encoding="UTF-8"?>\n<beans xmlns="http://www.springframework.org/schema/beans" \n    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n    xmlns:p="http://www.springframework.org/schema/p" \n    xmlns:context="http://www.springframework.org/schema/context"\n    xmlns:tx="http://www.springframework.org/schema/tx"\n    xsi:schemaLocation="\n        http://www.springframework.org/schema/beans\n        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd\n        http://www.springframework.org/schema/context\n        http://www.springframework.org/schema/context/spring-context-3.2.xsd\n        http://www.springframework.org/schema/tx\n        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">\n    <!-- 1. 数据源 : DriverManagerDataSource -->\n    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">\n        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>\n        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>\n        <property name="username" value="root"/>\n        <property name="password" value="root"/>\n    </bean>\n\n    <!-- \n        2. mybatis的SqlSession的工厂: SqlSessionFactoryBean \n            dataSource / typeAliasesPackage\n    -->\n    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">\n        <property name="dataSource" ref="datasource"/>\n        <property name="typeAliasesPackage" value="com/mybatis/test3/bean"/>\n    </bean>\n\n    <!-- \n        3. mybatis自动扫描加载Sql映射文件(即接口) : MapperScannerConfigurer \n            sqlSessionFactory / basePackage\n    -->\n    <bean id="config" class="org.mybatis.spring.mapper.MapperScannerConfigurer">\n        <property name="basePackage" value="com/mybatis/test3/mapper"/>\n        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>\n    </bean>\n\n    <!-- 4. 事务管理 : DataSourceTransactionManager -->\n    <bean id="manager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">\n        <property name="dataSource" ref="datasource"/>\n    </bean>\n\n    <!-- 5. 使用声明式事务 -->\n    <tx:annotation-driven transaction-manager="manager" />\n</beans>\n
\n

配置mybatis配置文件 里面没有配置内容(但是必须要)

\n
\n
<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">\n<configuration>\n\n</configuration>\n

n.分页插件 — PageHelper: 一个分页插件,支持多种数据库,原理大概是在执行sql语句之前(拦截器)进行了操作改写了sql语句,实现分页

    \n
  1. 导入依赖
  2. \n
\n
<dependency>\n    <groupId>com.github.pagehelper</groupId>\n    <artifactId>pagehelper</artifactId>\n</dependency>\n
    \n
  1. 配置插件–拦截器(在mybatis的配置文件中), 笔者在spring中集成了mybatis的配置
  2. \n
\n
<plugins>\n    <!--配置PageHelper插件-->\n    <plugin interceptor="com.github.pagehelper.PageHelper">\n        <!--配置方言(数据库识别)-->\n        <property name="dialect" value="mysql"/>\n    </plugin>\n</plugins>\n
    \n
  1. 测试分页
  2. \n
\n
@Test\npublic void testPage() {\n    //初始化Spring容器\n    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-dao.xml");\n\n    //获取代理对象\n    TbItemMapper itemMapper = applicationContext.getBean(TbItemMapper.class);\n\n    //执行sql语句前设置分页信息使用PageHelper的startPage方法\n    PageHelper.startPage(1, 10);\n\n    //查询\n    TbItemExample example = new TbItemExample();\n    List<TbItem> list = itemMapper.selectByExample(example);\n\n    //取分页信息PageInfo 总记录数 总页数 当前页\n    PageInfo<TbItem> pageInfo = new PageInfo<>(list);\n    System.out.println(pageInfo.getTotal());\n    System.out.println(pageInfo.getEndRow());\n    System.out.println(pageInfo.getFirstPage());\n    System.out.println(pageInfo.getLastPage());\n    System.out.println(pageInfo.getList());\n    System.out.println(pageInfo.getNavigatePages());\n    System.out.println(pageInfo.getNextPage());\n    System.out.println(pageInfo.getPageNum());\n    System.out.println(pageInfo.getPages());\n    System.out.println(pageInfo.getPageSize());\n    System.out.println(pageInfo.getPrePage());\n    System.out.println(pageInfo.getSize());\n    System.out.println(pageInfo.getStartRow());\n}\n
"},{"title":"VM options配置","comments":1,"description":null,"_content":"\n\n> 转载自 链接地址: http://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html\n> 转载自 链接地址: http://unixboy.iteye.com/blog/174173/\n\n\n\n# 文章一\n\nEclipse崩溃,错误提示:\nMyEclipse has detected that less than 5% of the 64MB of Perm \nGen (Non-heap memory) space remains. It is strongly recommended\nthat you exit and restart MyEclipse with new virtual machine memory\nparamters to increase this memory. Failure to do so can result in\ndata loss. The recommended Eclipse memory parameters are: \neclipse.exe -vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M\n\n1.参数的含义\n-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M\n-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了\n-Xms128m JVM初始分配的堆内存\n-Xmx512m JVM最大允许分配的堆内存,按需分配\n-XX:PermSize=64M JVM初始分配的非堆内存\n-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配\n\n我们首先了解一下JVM内存管理的机制,然后再解释每个参数代表的含义。\n\n\n## 堆(Heap)和非堆(Non-heap)内存\n\n\n 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。\n 可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,\n 所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。 \n\n\n### 堆内存分配\n\n\n JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;\n 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。\n 说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try...catch捕捉。 \n\n\n### 非堆内存分配\n\n\n JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,\n -server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)\n 上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。\nXX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。 \n说说为什么会内存益出: \n(1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。 \n(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。\n 这种错误常见在web服务器对JSP进行pre compile的时候。 \n\n\n## JVM内存限制(最大值)\n\n\n 首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,\n 这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。\n\n2. 为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?\n 通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:\n1) 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;\n2) -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,\n 如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。\n\n3. 为何将上面的参数写入到eclipse.ini文件Eclipse没有执行对应的设置?\n 那为什么同样的参数在快捷方式或者命令行中有效而在eclipse.ini文件中是无效的呢?这是因为我们没有遵守eclipse.ini文件的设置规则:\n参数形如“项 值”这种形式,中间有空格的需要换行书写,如果值中有空格的需要用双引号包括起来。比如我们使用-vm C:/Java/jre1.6.0/bin/javaw.exe参数设置虚拟机,\n在eclipse.ini文件中要写成这样:\n\n-vm \nC:/Java/jre1.6.0/bin/javaw.exe \n-vmargs \n-Xms128M \n-Xmx512M \n-XX:PermSize=64M \n-XX:MaxPermSize=128M \n\n实际运行的结果可以通过Eclipse中“Help”-“About Eclipse SDK”窗口里面的“Configuration Details”按钮进行查看。\n另外需要说明的是,Eclipse压缩包中自带的eclipse.ini文件内容是这样的:\n-showsplash \norg.eclipse.platform \n--launcher.XXMaxPermSize \n256m \n-vmargs \n-Xms40m \n-Xmx256m \n其中–launcher.XXMaxPermSize(注意最前面是两个连接线)跟-XX:MaxPermSize参数的含义基本是一样的,我觉得唯一的区别就是前者是eclipse.exe启动的时候设置的参数,\n而后者是eclipse所使用的JVM中的参数。其实二者设置一个就可以了,所以这里可以把–launcher.XXMaxPermSize和下一行使用#注释掉。\n\n4. 其他的启动参数。 如果你有一个双核的CPU,也许可以尝试这个参数:\n-XX:+UseParallelGC\n让GC可以更快的执行。(只是JDK 5里对GC新增加的参数)\n\n补充:\n  如果你的WEB APP下都用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。\n解决方法: 设置MaxPermSize大小 \n可以在myelipse里选中相应的服务器比如tomcat5,展开里面的JDK子项页面,来增加服务器启动的JVM参数设置:\n\n-Xms128m \n-Xmx256m \n-XX:PermSize=128M \n-XX:MaxNewSize=256m \n-XX:MaxPermSize=256m\n\n或者手动设置MaxPermSize大小,比如tomcat,\n修改TOMCAT_HOME/bin/catalina.bat,在echo \"Using CATALINA_BASE: $CATALINA_BASE\"上面加入以下行: \nJAVA_OPTS=\"-server -XX:PermSize=64M -XX:MaxPermSize=128m\n\n建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以减少jar 文档重复占用内存\n\n# 文章二\n\n## 1.堆大小设置\n\nJVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。\n\n### 典型设置:\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k\n\n-Xmx3550m:设置JVM最大可用内存为3550M。\n-Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。\n-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。\n-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。\n\n* java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0\n\n-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5\n-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6\n-XX:MaxPermSize=16m:设置持久代大小为16m。\n-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。\n\n## 2.回收器选择\n\nJVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。\n\n### 吞吐量优先的并行收集器\n如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。\n\n> 典型配置:\n\n* java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20\n-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。\n-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC\n-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100\n-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy\n-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。\n\n### 响应时间优先的并发收集器\n如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。\n\n> 典型配置:\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC\n-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。\n-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection\n-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。\n-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片\n\n> 辅助信息\n\nJVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:\n* -XX:+PrintGC\n输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]\n [Full GC 121376K->10414K(130112K), 0.0650971 secs]\n* -XX:+PrintGCDetails\n输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]\n [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]\n* -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用\n输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]\n* -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用\n输出形式:Application time: 0.5291524 seconds\n* -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用\n输出形式:Total time for which application threads were stopped: 0.0468229 seconds\n* -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息\n\n输出形式:\n\n 34.702: [GC {Heap before gc invocations=7:\n def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)\n eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)\n from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)\n to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)\n tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)\n the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)\n compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n 34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:\n def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)\n eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)\n from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)\n to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)\n tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)\n the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)\n compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n }\n , 0.0757599 secs]\n \n* -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。\n\n> 常见配置汇总\n\n1.堆设置\n* -Xms:初始堆大小\n* -Xmx:最大堆大小\n* -XX:NewSize=n:设置年轻代大小\n* -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4\n* -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5\n* -XX:MaxPermSize=n:设置持久代大小\n\n2.收集器设置\n* -XX:+UseSerialGC:设置串行收集器\n* -XX:+UseParallelGC:设置并行收集器\n* -XX:+UseParalledlOldGC:设置并行年老代收集器\n* -XX:+UseConcMarkSweepGC:设置并发收集器\n\n3.垃圾回收统计信息\n* -XX:+PrintGC\n* -XX:+PrintGCDetails\n* -XX:+PrintGCTimeStamps\n* -Xloggc:filename\n\n4.并行收集器设置\n* -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。\n* -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间\n* -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)\n\n5.并发收集器设置\n* -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。\n* -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。\n\n\n## 调优总结\n\n1.年轻代大小选择\n* 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。\n* 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。\n\n2.年老代大小选择\n* 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:\n\n 并发垃圾收集信息\n 持久代并发收集次数\n 传统GC信息\n 花在年轻代和年老代回收上的时间比例\n \n减少年轻代和年老代花费的时间,一般会提高应用的效率\n* 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。\n\n3.较小堆引起的碎片问题\n因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:\n\n* -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。\n* -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩\n\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/20170924_JVM3.md","raw":"---\ntitle: VM options配置\n\ncomments: true \n\ntags: \n - 深入了解java虚拟机\n - java\n\ncategories: \n - java虚拟机\n\ndescription:\n \n---\n\n\n> 转载自 链接地址: http://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html\n> 转载自 链接地址: http://unixboy.iteye.com/blog/174173/\n\n\n\n# 文章一\n\nEclipse崩溃,错误提示:\nMyEclipse has detected that less than 5% of the 64MB of Perm \nGen (Non-heap memory) space remains. It is strongly recommended\nthat you exit and restart MyEclipse with new virtual machine memory\nparamters to increase this memory. Failure to do so can result in\ndata loss. The recommended Eclipse memory parameters are: \neclipse.exe -vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M\n\n1.参数的含义\n-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M\n-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了\n-Xms128m JVM初始分配的堆内存\n-Xmx512m JVM最大允许分配的堆内存,按需分配\n-XX:PermSize=64M JVM初始分配的非堆内存\n-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配\n\n我们首先了解一下JVM内存管理的机制,然后再解释每个参数代表的含义。\n\n\n## 堆(Heap)和非堆(Non-heap)内存\n\n\n 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。\n 可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,\n 所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。 \n\n\n### 堆内存分配\n\n\n JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;\n 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。\n 说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try...catch捕捉。 \n\n\n### 非堆内存分配\n\n\n JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,\n -server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)\n 上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。\nXX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。 \n说说为什么会内存益出: \n(1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。 \n(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。\n 这种错误常见在web服务器对JSP进行pre compile的时候。 \n\n\n## JVM内存限制(最大值)\n\n\n 首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,\n 这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。\n\n2. 为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?\n 通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:\n1) 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;\n2) -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,\n 如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。\n\n3. 为何将上面的参数写入到eclipse.ini文件Eclipse没有执行对应的设置?\n 那为什么同样的参数在快捷方式或者命令行中有效而在eclipse.ini文件中是无效的呢?这是因为我们没有遵守eclipse.ini文件的设置规则:\n参数形如“项 值”这种形式,中间有空格的需要换行书写,如果值中有空格的需要用双引号包括起来。比如我们使用-vm C:/Java/jre1.6.0/bin/javaw.exe参数设置虚拟机,\n在eclipse.ini文件中要写成这样:\n\n-vm \nC:/Java/jre1.6.0/bin/javaw.exe \n-vmargs \n-Xms128M \n-Xmx512M \n-XX:PermSize=64M \n-XX:MaxPermSize=128M \n\n实际运行的结果可以通过Eclipse中“Help”-“About Eclipse SDK”窗口里面的“Configuration Details”按钮进行查看。\n另外需要说明的是,Eclipse压缩包中自带的eclipse.ini文件内容是这样的:\n-showsplash \norg.eclipse.platform \n--launcher.XXMaxPermSize \n256m \n-vmargs \n-Xms40m \n-Xmx256m \n其中–launcher.XXMaxPermSize(注意最前面是两个连接线)跟-XX:MaxPermSize参数的含义基本是一样的,我觉得唯一的区别就是前者是eclipse.exe启动的时候设置的参数,\n而后者是eclipse所使用的JVM中的参数。其实二者设置一个就可以了,所以这里可以把–launcher.XXMaxPermSize和下一行使用#注释掉。\n\n4. 其他的启动参数。 如果你有一个双核的CPU,也许可以尝试这个参数:\n-XX:+UseParallelGC\n让GC可以更快的执行。(只是JDK 5里对GC新增加的参数)\n\n补充:\n  如果你的WEB APP下都用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。\n解决方法: 设置MaxPermSize大小 \n可以在myelipse里选中相应的服务器比如tomcat5,展开里面的JDK子项页面,来增加服务器启动的JVM参数设置:\n\n-Xms128m \n-Xmx256m \n-XX:PermSize=128M \n-XX:MaxNewSize=256m \n-XX:MaxPermSize=256m\n\n或者手动设置MaxPermSize大小,比如tomcat,\n修改TOMCAT_HOME/bin/catalina.bat,在echo \"Using CATALINA_BASE: $CATALINA_BASE\"上面加入以下行: \nJAVA_OPTS=\"-server -XX:PermSize=64M -XX:MaxPermSize=128m\n\n建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以减少jar 文档重复占用内存\n\n# 文章二\n\n## 1.堆大小设置\n\nJVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。\n\n### 典型设置:\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k\n\n-Xmx3550m:设置JVM最大可用内存为3550M。\n-Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。\n-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。\n-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。\n\n* java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0\n\n-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5\n-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6\n-XX:MaxPermSize=16m:设置持久代大小为16m。\n-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。\n\n## 2.回收器选择\n\nJVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。\n\n### 吞吐量优先的并行收集器\n如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。\n\n> 典型配置:\n\n* java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20\n-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。\n-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC\n-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100\n-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy\n-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。\n\n### 响应时间优先的并发收集器\n如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。\n\n> 典型配置:\n\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC\n-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。\n-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。\n* java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection\n-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。\n-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片\n\n> 辅助信息\n\nJVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:\n* -XX:+PrintGC\n输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]\n [Full GC 121376K->10414K(130112K), 0.0650971 secs]\n* -XX:+PrintGCDetails\n输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]\n [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]\n* -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用\n输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]\n* -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用\n输出形式:Application time: 0.5291524 seconds\n* -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用\n输出形式:Total time for which application threads were stopped: 0.0468229 seconds\n* -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息\n\n输出形式:\n\n 34.702: [GC {Heap before gc invocations=7:\n def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)\n eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)\n from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)\n to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)\n tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)\n the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)\n compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n 34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:\n def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)\n eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)\n from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)\n to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)\n tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)\n the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)\n compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n }\n , 0.0757599 secs]\n \n* -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。\n\n> 常见配置汇总\n\n1.堆设置\n* -Xms:初始堆大小\n* -Xmx:最大堆大小\n* -XX:NewSize=n:设置年轻代大小\n* -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4\n* -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5\n* -XX:MaxPermSize=n:设置持久代大小\n\n2.收集器设置\n* -XX:+UseSerialGC:设置串行收集器\n* -XX:+UseParallelGC:设置并行收集器\n* -XX:+UseParalledlOldGC:设置并行年老代收集器\n* -XX:+UseConcMarkSweepGC:设置并发收集器\n\n3.垃圾回收统计信息\n* -XX:+PrintGC\n* -XX:+PrintGCDetails\n* -XX:+PrintGCTimeStamps\n* -Xloggc:filename\n\n4.并行收集器设置\n* -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。\n* -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间\n* -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)\n\n5.并发收集器设置\n* -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。\n* -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。\n\n\n## 调优总结\n\n1.年轻代大小选择\n* 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。\n* 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。\n\n2.年老代大小选择\n* 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:\n\n 并发垃圾收集信息\n 持久代并发收集次数\n 传统GC信息\n 花在年轻代和年老代回收上的时间比例\n \n减少年轻代和年老代花费的时间,一般会提高应用的效率\n* 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。\n\n3.较小堆引起的碎片问题\n因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:\n\n* -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。\n* -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩\n\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"20170924_JVM3","published":1,"date":"2020-01-15T05:50:39.643Z","updated":"2021-03-10T13:50:15.316Z","layout":"post","photos":[],"link":"","_id":"ckm3invgo009324uj3cdg0fej","content":"
\n

转载自 链接地址: http://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html
转载自 链接地址: http://unixboy.iteye.com/blog/174173/

\n
\n\n

文章一

Eclipse崩溃,错误提示:
MyEclipse has detected that less than 5% of the 64MB of Perm
Gen (Non-heap memory) space remains. It is strongly recommended
that you exit and restart MyEclipse with new virtual machine memory
paramters to increase this memory. Failure to do so can result in
data loss. The recommended Eclipse memory parameters are:
eclipse.exe -vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M

\n

1.参数的含义
-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M
-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了
-Xms128m JVM初始分配的堆内存
-Xmx512m JVM最大允许分配的堆内存,按需分配
-XX:PermSize=64M JVM初始分配的非堆内存
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配

\n

我们首先了解一下JVM内存管理的机制,然后再解释每个参数代表的含义。

\n

堆(Heap)和非堆(Non-heap)内存

按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。
可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,
所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

\n

堆内存分配

JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try…catch捕捉。

\n

非堆内存分配

JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,
-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)
上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。
XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。
说说为什么会内存益出:
(1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。
(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。
这种错误常见在web服务器对JSP进行pre compile的时候。

\n

JVM内存限制(最大值)

首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,
这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。

\n
    \n
  1. 为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?
    通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:
    1) 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;
    2) -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,
    如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。

    \n
  2. \n
  3. 为何将上面的参数写入到eclipse.ini文件Eclipse没有执行对应的设置?
    那为什么同样的参数在快捷方式或者命令行中有效而在eclipse.ini文件中是无效的呢?这是因为我们没有遵守eclipse.ini文件的设置规则:
    参数形如“项 值”这种形式,中间有空格的需要换行书写,如果值中有空格的需要用双引号包括起来。比如我们使用-vm C:/Java/jre1.6.0/bin/javaw.exe参数设置虚拟机,
    在eclipse.ini文件中要写成这样:

    \n
  4. \n
\n

-vm
C:/Java/jre1.6.0/bin/javaw.exe
-vmargs
-Xms128M
-Xmx512M
-XX:PermSize=64M
-XX:MaxPermSize=128M

\n

实际运行的结果可以通过Eclipse中“Help”-“About Eclipse SDK”窗口里面的“Configuration Details”按钮进行查看。
另外需要说明的是,Eclipse压缩包中自带的eclipse.ini文件内容是这样的:
-showsplash
org.eclipse.platform
–launcher.XXMaxPermSize
256m
-vmargs
-Xms40m
-Xmx256m
其中–launcher.XXMaxPermSize(注意最前面是两个连接线)跟-XX:MaxPermSize参数的含义基本是一样的,我觉得唯一的区别就是前者是eclipse.exe启动的时候设置的参数,
而后者是eclipse所使用的JVM中的参数。其实二者设置一个就可以了,所以这里可以把–launcher.XXMaxPermSize和下一行使用#注释掉。

\n
    \n
  1. 其他的启动参数。 如果你有一个双核的CPU,也许可以尝试这个参数:
    -XX:+UseParallelGC
    让GC可以更快的执行。(只是JDK 5里对GC新增加的参数)
  2. \n
\n

补充:
  如果你的WEB APP下都用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。
解决方法: 设置MaxPermSize大小
可以在myelipse里选中相应的服务器比如tomcat5,展开里面的JDK子项页面,来增加服务器启动的JVM参数设置:

\n

-Xms128m
-Xmx256m
-XX:PermSize=128M
-XX:MaxNewSize=256m
-XX:MaxPermSize=256m

\n

或者手动设置MaxPermSize大小,比如tomcat,
修改TOMCAT_HOME/bin/catalina.bat,在echo “Using CATALINA_BASE: $CATALINA_BASE”上面加入以下行:
JAVA_OPTS=”-server -XX:PermSize=64M -XX:MaxPermSize=128m

\n

建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以减少jar 文档重复占用内存

\n

文章二

1.堆大小设置

JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。

\n

典型设置:

    \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
  • \n
\n

-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

\n
    \n
  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
  • \n
\n

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

\n

2.回收器选择

JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

\n

吞吐量优先的并行收集器

如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

\n
\n

典型配置:

\n
\n
    \n
  • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
    -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
    -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

    \n
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
    -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。

    \n
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
    -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

    \n
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
    -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

    \n
  • \n
\n

响应时间优先的并发收集器

如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

\n
\n

典型配置:

\n
\n
    \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
    -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
    -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
    -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
  • \n
\n
\n

辅助信息

\n
\n

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

\n
    \n
  • -XX:+PrintGC
    输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
    [Full GC 121376K->10414K(130112K), 0.0650971 secs]\n
  • \n
  • -XX:+PrintGCDetails
    输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
    [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]\n
  • \n
  • -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
    输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
  • \n
  • -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
    输出形式:Application time: 0.5291524 seconds
  • \n
  • -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
    输出形式:Total time for which application threads were stopped: 0.0468229 seconds
  • \n
  • -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
  • \n
\n

输出形式:

\n
34.702: [GC {Heap before gc invocations=7:\n def new generation   total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)\neden space 49152K,  99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)\nfrom space 6144K,  55% used [0x221d0000, 0x22527e10, 0x227d0000)\n  to   space 6144K,   0% used [0x21bd0000, 0x21bd0000, 0x221d0000)\n tenured generation   total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)\nthe space 69632K,   3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)\n compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n    ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n    rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:\n def new generation   total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)\neden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)\n  from space 6144K,  55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)\n  to   space 6144K,   0% used [0x221d0000, 0x221d0000, 0x227d0000)\n tenured generation   total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)\nthe space 69632K,   4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)\n compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n    ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n    rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n}\n, 0.0757599 secs]\n
    \n
  • -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
  • \n
\n
\n

常见配置汇总

\n
\n

1.堆设置

\n
    \n
  • -Xms:初始堆大小
  • \n
  • -Xmx:最大堆大小
  • \n
  • -XX:NewSize=n:设置年轻代大小
  • \n
  • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
  • \n
  • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
  • \n
  • -XX:MaxPermSize=n:设置持久代大小
  • \n
\n

2.收集器设置

\n
    \n
  • -XX:+UseSerialGC:设置串行收集器
  • \n
  • -XX:+UseParallelGC:设置并行收集器
  • \n
  • -XX:+UseParalledlOldGC:设置并行年老代收集器
  • \n
  • -XX:+UseConcMarkSweepGC:设置并发收集器
  • \n
\n

3.垃圾回收统计信息

\n
    \n
  • -XX:+PrintGC
  • \n
  • -XX:+PrintGCDetails
  • \n
  • -XX:+PrintGCTimeStamps
  • \n
  • -Xloggc:filename
  • \n
\n

4.并行收集器设置

\n
    \n
  • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
  • \n
  • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
  • \n
  • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  • \n
\n

5.并发收集器设置

\n
    \n
  • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
  • \n
  • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
  • \n
\n

调优总结

1.年轻代大小选择

\n
    \n
  • 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
  • \n
  • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
  • \n
\n

2.年老代大小选择

\n
    \n
  • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

    \n
    并发垃圾收集信息\n持久代并发收集次数\n传统GC信息\n花在年轻代和年老代回收上的时间比例\n
  • \n
\n

减少年轻代和年老代花费的时间,一般会提高应用的效率

\n
    \n
  • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
  • \n
\n

3.较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:

\n
    \n
  • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
  • \n
  • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
  • \n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"
\n

转载自 链接地址: http://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html
转载自 链接地址: http://unixboy.iteye.com/blog/174173/

\n
","more":"

文章一

Eclipse崩溃,错误提示:
MyEclipse has detected that less than 5% of the 64MB of Perm
Gen (Non-heap memory) space remains. It is strongly recommended
that you exit and restart MyEclipse with new virtual machine memory
paramters to increase this memory. Failure to do so can result in
data loss. The recommended Eclipse memory parameters are:
eclipse.exe -vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M

\n

1.参数的含义
-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M
-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了
-Xms128m JVM初始分配的堆内存
-Xmx512m JVM最大允许分配的堆内存,按需分配
-XX:PermSize=64M JVM初始分配的非堆内存
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配

\n

我们首先了解一下JVM内存管理的机制,然后再解释每个参数代表的含义。

\n

堆(Heap)和非堆(Non-heap)内存

按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。
可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,
所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

\n

堆内存分配

JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try…catch捕捉。

\n

非堆内存分配

JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,
-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)
上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。
XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。
说说为什么会内存益出:
(1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。
(2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。
这种错误常见在web服务器对JSP进行pre compile的时候。

\n

JVM内存限制(最大值)

首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,
这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。

\n
    \n
  1. 为什么有的机器我将-Xmx和-XX:MaxPermSize都设置为512M之后Eclipse可以启动,而有些机器无法启动?
    通过上面对JVM内存管理的介绍我们已经了解到JVM内存包含两种:堆内存和非堆内存,另外JVM最大内存首先取决于实际的物理内存和操作系统。所以说设置VM参数导致程序无法启动主要有以下几种原因:
    1) 参数中-Xms的值大于-Xmx,或者-XX:PermSize的值大于-XX:MaxPermSize;
    2) -Xmx的值和-XX:MaxPermSize的总和超过了JVM内存的最大限制,比如当前操作系统最大内存限制,或者实际的物理内存等等。说到实际物理内存这里需要说明一点的是,
    如果你的内存是1024MB,但实际系统中用到的并不可能是1024MB,因为有一部分被硬件占用了。

    \n
  2. \n
  3. 为何将上面的参数写入到eclipse.ini文件Eclipse没有执行对应的设置?
    那为什么同样的参数在快捷方式或者命令行中有效而在eclipse.ini文件中是无效的呢?这是因为我们没有遵守eclipse.ini文件的设置规则:
    参数形如“项 值”这种形式,中间有空格的需要换行书写,如果值中有空格的需要用双引号包括起来。比如我们使用-vm C:/Java/jre1.6.0/bin/javaw.exe参数设置虚拟机,
    在eclipse.ini文件中要写成这样:

    \n
  4. \n
\n

-vm
C:/Java/jre1.6.0/bin/javaw.exe
-vmargs
-Xms128M
-Xmx512M
-XX:PermSize=64M
-XX:MaxPermSize=128M

\n

实际运行的结果可以通过Eclipse中“Help”-“About Eclipse SDK”窗口里面的“Configuration Details”按钮进行查看。
另外需要说明的是,Eclipse压缩包中自带的eclipse.ini文件内容是这样的:
-showsplash
org.eclipse.platform
–launcher.XXMaxPermSize
256m
-vmargs
-Xms40m
-Xmx256m
其中–launcher.XXMaxPermSize(注意最前面是两个连接线)跟-XX:MaxPermSize参数的含义基本是一样的,我觉得唯一的区别就是前者是eclipse.exe启动的时候设置的参数,
而后者是eclipse所使用的JVM中的参数。其实二者设置一个就可以了,所以这里可以把–launcher.XXMaxPermSize和下一行使用#注释掉。

\n
    \n
  1. 其他的启动参数。 如果你有一个双核的CPU,也许可以尝试这个参数:
    -XX:+UseParallelGC
    让GC可以更快的执行。(只是JDK 5里对GC新增加的参数)
  2. \n
\n

补充:
  如果你的WEB APP下都用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。
解决方法: 设置MaxPermSize大小
可以在myelipse里选中相应的服务器比如tomcat5,展开里面的JDK子项页面,来增加服务器启动的JVM参数设置:

\n

-Xms128m
-Xmx256m
-XX:PermSize=128M
-XX:MaxNewSize=256m
-XX:MaxPermSize=256m

\n

或者手动设置MaxPermSize大小,比如tomcat,
修改TOMCAT_HOME/bin/catalina.bat,在echo “Using CATALINA_BASE: $CATALINA_BASE”上面加入以下行:
JAVA_OPTS=”-server -XX:PermSize=64M -XX:MaxPermSize=128m

\n

建议:将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以减少jar 文档重复占用内存

\n

文章二

1.堆大小设置

JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。

\n

典型设置:

    \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
  • \n
\n

-Xmx3550m:设置JVM最大可用内存为3550M。
-Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

\n
    \n
  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
  • \n
\n

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

\n

2.回收器选择

JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

\n

吞吐量优先的并行收集器

如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

\n
\n

典型配置:

\n
\n
    \n
  • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
    -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
    -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

    \n
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
    -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。

    \n
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
    -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

    \n
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
    -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

    \n
  • \n
\n

响应时间优先的并发收集器

如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

\n
\n

典型配置:

\n
\n
    \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
    -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
  • \n
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
    -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
    -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
  • \n
\n
\n

辅助信息

\n
\n

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

\n
    \n
  • -XX:+PrintGC
    输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
    [Full GC 121376K->10414K(130112K), 0.0650971 secs]\n
  • \n
  • -XX:+PrintGCDetails
    输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
    [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]\n
  • \n
  • -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
    输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
  • \n
  • -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
    输出形式:Application time: 0.5291524 seconds
  • \n
  • -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
    输出形式:Total time for which application threads were stopped: 0.0468229 seconds
  • \n
  • -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
  • \n
\n

输出形式:

\n
34.702: [GC {Heap before gc invocations=7:\n def new generation   total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)\neden space 49152K,  99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)\nfrom space 6144K,  55% used [0x221d0000, 0x22527e10, 0x227d0000)\n  to   space 6144K,   0% used [0x21bd0000, 0x21bd0000, 0x221d0000)\n tenured generation   total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)\nthe space 69632K,   3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)\n compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n    ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n    rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:\n def new generation   total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)\neden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)\n  from space 6144K,  55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)\n  to   space 6144K,   0% used [0x221d0000, 0x221d0000, 0x227d0000)\n tenured generation   total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)\nthe space 69632K,   4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)\n compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)\n   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)\n    ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)\n    rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)\n}\n, 0.0757599 secs]\n
    \n
  • -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
  • \n
\n
\n

常见配置汇总

\n
\n

1.堆设置

\n
    \n
  • -Xms:初始堆大小
  • \n
  • -Xmx:最大堆大小
  • \n
  • -XX:NewSize=n:设置年轻代大小
  • \n
  • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
  • \n
  • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
  • \n
  • -XX:MaxPermSize=n:设置持久代大小
  • \n
\n

2.收集器设置

\n
    \n
  • -XX:+UseSerialGC:设置串行收集器
  • \n
  • -XX:+UseParallelGC:设置并行收集器
  • \n
  • -XX:+UseParalledlOldGC:设置并行年老代收集器
  • \n
  • -XX:+UseConcMarkSweepGC:设置并发收集器
  • \n
\n

3.垃圾回收统计信息

\n
    \n
  • -XX:+PrintGC
  • \n
  • -XX:+PrintGCDetails
  • \n
  • -XX:+PrintGCTimeStamps
  • \n
  • -Xloggc:filename
  • \n
\n

4.并行收集器设置

\n
    \n
  • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
  • \n
  • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
  • \n
  • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  • \n
\n

5.并发收集器设置

\n
    \n
  • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
  • \n
  • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
  • \n
\n

调优总结

1.年轻代大小选择

\n
    \n
  • 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
  • \n
  • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
  • \n
\n

2.年老代大小选择

\n
    \n
  • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

    \n
    并发垃圾收集信息\n持久代并发收集次数\n传统GC信息\n花在年轻代和年老代回收上的时间比例\n
  • \n
\n

减少年轻代和年老代花费的时间,一般会提高应用的效率

\n
    \n
  • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
  • \n
\n

3.较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:

\n
    \n
  • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
  • \n
  • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩
  • \n
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
"},{"title":"彻底理解ThreadLocal(转载)","comments":1,"description":null,"date":"2017-02-14T16:00:00.000Z","_content":"\nhttp://blog.csdn.net/lufeng20/article/details/24314381\n\n## ThreadLocal是什么\n  早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。\n  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。\n  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。\n  所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。\nThreadLocal的接口方法\nThreadLocal类接口很简单,只有4个方法,我们先来了解一下:\nvoid set(Object value)设置当前线程的线程局部变量的值。\npublic Object get()该方法返回当前线程所对应的线程局部变量。\npublic void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。\nprotected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。\n  值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。\n  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n\n\n\n package com.test; \n \n public class TestNum { \n // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 \n private static ThreadLocal seqNum = new ThreadLocal() { \n public Integer initialValue() { \n return 0; \n } \n }; \n \n // ②获取下一个序列值 \n public int getNextNum() { \n seqNum.set(seqNum.get() + 1); \n return seqNum.get(); \n } \n \n public static void main(String[] args) { \n TestNum sn = new TestNum(); \n // ③ 3个线程共享sn,各自产生序列号 \n TestClient t1 = new TestClient(sn); \n TestClient t2 = new TestClient(sn); \n TestClient t3 = new TestClient(sn); \n t1.start(); \n t2.start(); \n t3.start(); \n } \n \n private static class TestClient extends Thread { \n private TestNum sn; \n \n public TestClient(TestNum sn) { \n this.sn = sn; \n } \n \n public void run() { \n for (int i = 0; i < 3; i++) { \n // ④每个线程打出3个序列值 \n System.out.println(\"thread[\" + Thread.currentThread().getName() + \"] --> sn[\" \n + sn.getNextNum() + \"]\"); \n } \n } \n } \n } \n\n\n 通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个TestNum实例。运行以上代码,在控制台上输出以下的结果:\nthread[Thread-0] --> sn[1]\nthread[Thread-1] --> sn[1]\nthread[Thread-2] --> sn[1]\nthread[Thread-1] --> sn[2]\nthread[Thread-0] --> sn[2]\nthread[Thread-1] --> sn[3]\nthread[Thread-2] --> sn[2]\nthread[Thread-0] --> sn[3]\nthread[Thread-2] --> sn[3]\n考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。\n\nThread同步机制的比较\n  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。\n  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。\n  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。\n  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal版本。\n  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。\n  spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。\n  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:\n通通透透理解ThreadLocal\n  同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。\n  下面的实例能够体现Spring对有状态Bean的改造思路:\n代码清单3 TestDao:非线程安全\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n package com.test; \n \n import java.sql.Connection; \n import java.sql.SQLException; \n import java.sql.Statement; \n \n public class TestDao { \n private Connection conn;// ①一个非线程安全的变量 \n \n public void addTopic() throws SQLException { \n Statement stat = conn.createStatement();// ②引用非线程安全变量 \n // … \n } \n } \n\n\n\n由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:\n代码清单4 TestDao:线程安全\n\n package com.test; \n \n import java.sql.Connection; \n import java.sql.SQLException; \n import java.sql.Statement; \n \n public class TestDaoNew { \n // ①使用ThreadLocal保存Connection变量 \n private static ThreadLocal connThreadLocal = new ThreadLocal(); \n \n public static Connection getConnection() { \n // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection, \n // 并将其保存到线程本地变量中。 \n if (connThreadLocal.get() == null) { \n Connection conn = getConnection(); \n connThreadLocal.set(conn); \n return conn; \n } else { \n return connThreadLocal.get();// ③直接返回线程本地变量 \n } \n } \n \n public void addTopic() throws SQLException { \n // ④从ThreadLocal中获取线程对应的Connection \n Statement stat = getConnection().createStatement(); \n } \n } \n\n不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。\n当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。\n\n\nConnectionManager.java\n\n package com.test; \n \n import java.sql.Connection; \n import java.sql.DriverManager; \n import java.sql.SQLException; \n \n public class ConnectionManager { \n \n private static ThreadLocal connectionHolder = new ThreadLocal() { \n @Override \n protected Connection initialValue() { \n Connection conn = null; \n try { \n conn = DriverManager.getConnection( \n \"jdbc:mysql://localhost:3306/test\", \"username\", \n \"password\"); \n } catch (SQLException e) { \n e.printStackTrace(); \n } \n return conn; \n } \n }; \n \n public static Connection getConnection() { \n return connectionHolder.get(); \n } \n \n public static void setConnection(Connection conn) { \n connectionHolder.set(conn); \n } \n } \n \n\njava.lang.ThreadLocal的具体实现\n那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n \n /** \n * Sets the current thread's copy of this thread-local variable \n * to the specified value. Most subclasses will have no need to \n * override this method, relying solely on the {@link #initialValue} \n * method to set the values of thread-locals. \n * \n * @param value the value to be stored in the current thread's copy of \n * this thread-local. \n */ \n public void set(T value) { \n Thread t = Thread.currentThread(); \n ThreadLocalMap map = getMap(t); \n if (map != null) \n map.set(this, value); \n else \n createMap(t, value); \n } \n\n在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。\n\n\n线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。\n\n\n为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n \n /** \n * Get the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param t the current thread \n * @return the map \n */ \n ThreadLocalMap getMap(Thread t) { \n return t.threadLocals; \n } \n \n /** \n * Create the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param t the current thread \n * @param firstValue value for the initial entry of the map \n * @param map the map to store. \n */ \n void createMap(Thread t, T firstValue) { \n t.threadLocals = new ThreadLocalMap(this, firstValue); \n } \n \n\n接下来再看一下ThreadLocal类中的get()方法:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n \n /** \n * Returns the value in the current thread's copy of this \n * thread-local variable. If the variable has no value for the \n * current thread, it is first initialized to the value returned \n * by an invocation of the {@link #initialValue} method. \n * \n * @return the current thread's value of this thread-local \n */ \n public T get() { \n Thread t = Thread.currentThread(); \n ThreadLocalMap map = getMap(t); \n if (map != null) { \n ThreadLocalMap.Entry e = map.getEntry(this); \n if (e != null) \n return (T)e.value; \n } \n return setInitialValue(); \n } \n\n\n再来看setInitialValue()方法:\n\n \n /** \n * Variant of set() to establish initialValue. Used instead \n * of set() in case user has overridden the set() method. \n * \n * @return the initial value \n */ \n private T setInitialValue() { \n T value = initialValue(); \n Thread t = Thread.currentThread(); \n ThreadLocalMap map = getMap(t); \n if (map != null) \n map.set(this, value); \n else \n createMap(t, value); \n return value; \n } \n\n\n  获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。\n\n\n  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。\n\n小结\n  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。\nConnectionManager.java\n\n \n package com.test; \n \n import java.sql.Connection; \n import java.sql.DriverManager; \n import java.sql.SQLException; \n \n public class ConnectionManager { \n \n private static ThreadLocal connectionHolder = new ThreadLocal() { \n @Override \n protected Connection initialValue() { \n Connection conn = null; \n try { \n conn = DriverManager.getConnection( \n \"jdbc:mysql://localhost:3306/test\", \"username\", \n \"password\"); \n } catch (SQLException e) { \n e.printStackTrace(); \n } \n return conn; \n } \n }; \n \n public static Connection getConnection() { \n return connectionHolder.get(); \n } \n \n public static void setConnection(Connection conn) { \n connectionHolder.set(conn); \n } \n } ","source":"_posts/2017_ThreadLocal.md","raw":"---\ntitle: 彻底理解ThreadLocal(转载)\n\ncomments: true \n\ntags: \n - ThreadLocal\n\ncategories: \n - 多线程\n\ndescription: \n\ndate: 2017-02-15\n \n---\n\nhttp://blog.csdn.net/lufeng20/article/details/24314381\n\n## ThreadLocal是什么\n  早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。\n  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。\n  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。\n  所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。\nThreadLocal的接口方法\nThreadLocal类接口很简单,只有4个方法,我们先来了解一下:\nvoid set(Object value)设置当前线程的线程局部变量的值。\npublic Object get()该方法返回当前线程所对应的线程局部变量。\npublic void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。\nprotected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。\n  值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。\n  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n\n\n\n package com.test; \n \n public class TestNum { \n // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 \n private static ThreadLocal seqNum = new ThreadLocal() { \n public Integer initialValue() { \n return 0; \n } \n }; \n \n // ②获取下一个序列值 \n public int getNextNum() { \n seqNum.set(seqNum.get() + 1); \n return seqNum.get(); \n } \n \n public static void main(String[] args) { \n TestNum sn = new TestNum(); \n // ③ 3个线程共享sn,各自产生序列号 \n TestClient t1 = new TestClient(sn); \n TestClient t2 = new TestClient(sn); \n TestClient t3 = new TestClient(sn); \n t1.start(); \n t2.start(); \n t3.start(); \n } \n \n private static class TestClient extends Thread { \n private TestNum sn; \n \n public TestClient(TestNum sn) { \n this.sn = sn; \n } \n \n public void run() { \n for (int i = 0; i < 3; i++) { \n // ④每个线程打出3个序列值 \n System.out.println(\"thread[\" + Thread.currentThread().getName() + \"] --> sn[\" \n + sn.getNextNum() + \"]\"); \n } \n } \n } \n } \n\n\n 通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个TestNum实例。运行以上代码,在控制台上输出以下的结果:\nthread[Thread-0] --> sn[1]\nthread[Thread-1] --> sn[1]\nthread[Thread-2] --> sn[1]\nthread[Thread-1] --> sn[2]\nthread[Thread-0] --> sn[2]\nthread[Thread-1] --> sn[3]\nthread[Thread-2] --> sn[2]\nthread[Thread-0] --> sn[3]\nthread[Thread-2] --> sn[3]\n考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。\n\nThread同步机制的比较\n  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。\n  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。\n  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。\n  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal版本。\n  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。\n  spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。\n  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:\n通通透透理解ThreadLocal\n  同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。\n  下面的实例能够体现Spring对有状态Bean的改造思路:\n代码清单3 TestDao:非线程安全\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n package com.test; \n \n import java.sql.Connection; \n import java.sql.SQLException; \n import java.sql.Statement; \n \n public class TestDao { \n private Connection conn;// ①一个非线程安全的变量 \n \n public void addTopic() throws SQLException { \n Statement stat = conn.createStatement();// ②引用非线程安全变量 \n // … \n } \n } \n\n\n\n由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:\n代码清单4 TestDao:线程安全\n\n package com.test; \n \n import java.sql.Connection; \n import java.sql.SQLException; \n import java.sql.Statement; \n \n public class TestDaoNew { \n // ①使用ThreadLocal保存Connection变量 \n private static ThreadLocal connThreadLocal = new ThreadLocal(); \n \n public static Connection getConnection() { \n // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection, \n // 并将其保存到线程本地变量中。 \n if (connThreadLocal.get() == null) { \n Connection conn = getConnection(); \n connThreadLocal.set(conn); \n return conn; \n } else { \n return connThreadLocal.get();// ③直接返回线程本地变量 \n } \n } \n \n public void addTopic() throws SQLException { \n // ④从ThreadLocal中获取线程对应的Connection \n Statement stat = getConnection().createStatement(); \n } \n } \n\n不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。\n当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。\n\n\nConnectionManager.java\n\n package com.test; \n \n import java.sql.Connection; \n import java.sql.DriverManager; \n import java.sql.SQLException; \n \n public class ConnectionManager { \n \n private static ThreadLocal connectionHolder = new ThreadLocal() { \n @Override \n protected Connection initialValue() { \n Connection conn = null; \n try { \n conn = DriverManager.getConnection( \n \"jdbc:mysql://localhost:3306/test\", \"username\", \n \"password\"); \n } catch (SQLException e) { \n e.printStackTrace(); \n } \n return conn; \n } \n }; \n \n public static Connection getConnection() { \n return connectionHolder.get(); \n } \n \n public static void setConnection(Connection conn) { \n connectionHolder.set(conn); \n } \n } \n \n\njava.lang.ThreadLocal的具体实现\n那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n \n /** \n * Sets the current thread's copy of this thread-local variable \n * to the specified value. Most subclasses will have no need to \n * override this method, relying solely on the {@link #initialValue} \n * method to set the values of thread-locals. \n * \n * @param value the value to be stored in the current thread's copy of \n * this thread-local. \n */ \n public void set(T value) { \n Thread t = Thread.currentThread(); \n ThreadLocalMap map = getMap(t); \n if (map != null) \n map.set(this, value); \n else \n createMap(t, value); \n } \n\n在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。\n\n\n线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。\n\n\n为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n \n /** \n * Get the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param t the current thread \n * @return the map \n */ \n ThreadLocalMap getMap(Thread t) { \n return t.threadLocals; \n } \n \n /** \n * Create the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param t the current thread \n * @param firstValue value for the initial entry of the map \n * @param map the map to store. \n */ \n void createMap(Thread t, T firstValue) { \n t.threadLocals = new ThreadLocalMap(this, firstValue); \n } \n \n\n接下来再看一下ThreadLocal类中的get()方法:\n[java] view plain copy print?在CODE上查看代码片派生到我的代码片\n\n \n /** \n * Returns the value in the current thread's copy of this \n * thread-local variable. If the variable has no value for the \n * current thread, it is first initialized to the value returned \n * by an invocation of the {@link #initialValue} method. \n * \n * @return the current thread's value of this thread-local \n */ \n public T get() { \n Thread t = Thread.currentThread(); \n ThreadLocalMap map = getMap(t); \n if (map != null) { \n ThreadLocalMap.Entry e = map.getEntry(this); \n if (e != null) \n return (T)e.value; \n } \n return setInitialValue(); \n } \n\n\n再来看setInitialValue()方法:\n\n \n /** \n * Variant of set() to establish initialValue. Used instead \n * of set() in case user has overridden the set() method. \n * \n * @return the initial value \n */ \n private T setInitialValue() { \n T value = initialValue(); \n Thread t = Thread.currentThread(); \n ThreadLocalMap map = getMap(t); \n if (map != null) \n map.set(this, value); \n else \n createMap(t, value); \n return value; \n } \n\n\n  获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。\n\n\n  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。\n\n小结\n  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。\nConnectionManager.java\n\n \n package com.test; \n \n import java.sql.Connection; \n import java.sql.DriverManager; \n import java.sql.SQLException; \n \n public class ConnectionManager { \n \n private static ThreadLocal connectionHolder = new ThreadLocal() { \n @Override \n protected Connection initialValue() { \n Connection conn = null; \n try { \n conn = DriverManager.getConnection( \n \"jdbc:mysql://localhost:3306/test\", \"username\", \n \"password\"); \n } catch (SQLException e) { \n e.printStackTrace(); \n } \n return conn; \n } \n }; \n \n public static Connection getConnection() { \n return connectionHolder.get(); \n } \n \n public static void setConnection(Connection conn) { \n connectionHolder.set(conn); \n } \n } ","slug":"2017_ThreadLocal","published":1,"updated":"2018-01-13T02:29:22.233Z","layout":"post","photos":[],"link":"","_id":"ckm3invgp009524ujtdoqd41d","content":"

http://blog.csdn.net/lufeng20/article/details/24314381

\n

ThreadLocal是什么

  早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
  所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)设置当前线程的线程局部变量的值。
public Object get()该方法返回当前线程所对应的线程局部变量。
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
  值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n\n
package com.test;  \n\npublic class TestNum {  \n    // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  \n    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  \n        public Integer initialValue() {  \n            return 0;  \n        }  \n    };  \n\n    // ②获取下一个序列值  \n    public int getNextNum() {  \n        seqNum.set(seqNum.get() + 1);  \n        return seqNum.get();  \n    }  \n\n    public static void main(String[] args) {  \n        TestNum sn = new TestNum();  \n        // ③ 3个线程共享sn,各自产生序列号  \n        TestClient t1 = new TestClient(sn);  \n        TestClient t2 = new TestClient(sn);  \n        TestClient t3 = new TestClient(sn);  \n        t1.start();  \n        t2.start();  \n        t3.start();  \n    }  \n\n    private static class TestClient extends Thread {  \n        private TestNum sn;  \n\n        public TestClient(TestNum sn) {  \n            this.sn = sn;  \n        }  \n\n        public void run() {  \n            for (int i = 0; i < 3; i++) {  \n                // ④每个线程打出3个序列值  \n                System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  \n                         + sn.getNextNum() + "]");  \n            }  \n        }  \n    }  \n}  \n

通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个TestNum实例。运行以上代码,在控制台上输出以下的结果:
thread[Thread-0] –> sn[1]
thread[Thread-1] –> sn[1]
thread[Thread-2] –> sn[1]
thread[Thread-1] –> sn[2]
thread[Thread-0] –> sn[2]
thread[Thread-1] –> sn[3]
thread[Thread-2] –> sn[2]
thread[Thread-0] –> sn[3]
thread[Thread-2] –> sn[3]
考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

\n

Thread同步机制的比较
  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal版本。
  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
  spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:
通通透透理解ThreadLocal
  同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。
  下面的实例能够体现Spring对有状态Bean的改造思路:
代码清单3 TestDao:非线程安全
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.SQLException;  \nimport java.sql.Statement;  \n\npublic class TestDao {  \n    private Connection conn;// ①一个非线程安全的变量  \n\n    public void addTopic() throws SQLException {  \n        Statement stat = conn.createStatement();// ②引用非线程安全变量  \n        // …  \n    }  \n}  \n

由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:
代码清单4 TestDao:线程安全

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.SQLException;  \nimport java.sql.Statement;  \n\npublic class TestDaoNew {  \n    // ①使用ThreadLocal保存Connection变量  \n    private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();  \n\n    public static Connection getConnection() {  \n        // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,  \n        // 并将其保存到线程本地变量中。  \n        if (connThreadLocal.get() == null) {  \n            Connection conn = getConnection();  \n            connThreadLocal.set(conn);  \n            return conn;  \n        } else {  \n            return connThreadLocal.get();// ③直接返回线程本地变量  \n        }  \n    }  \n\n    public void addTopic() throws SQLException {  \n        // ④从ThreadLocal中获取线程对应的Connection  \n        Statement stat = getConnection().createStatement();  \n    }  \n}  \n

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。
当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

\n

ConnectionManager.java

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.DriverManager;  \nimport java.sql.SQLException;  \n\npublic class ConnectionManager {  \n\n    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  \n        @Override  \n        protected Connection initialValue() {  \n            Connection conn = null;  \n            try {  \n                conn = DriverManager.getConnection(  \n                        "jdbc:mysql://localhost:3306/test", "username",  \n                        "password");  \n            } catch (SQLException e) {  \n                e.printStackTrace();  \n            }  \n            return conn;  \n        }  \n    };  \n\n    public static Connection getConnection() {  \n        return connectionHolder.get();  \n    }  \n\n    public static void setConnection(Connection conn) {  \n        connectionHolder.set(conn);  \n    }  \n}  \n

java.lang.ThreadLocal的具体实现
那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
/** \n    * Sets the current thread's copy of this thread-local variable \n    * to the specified value.  Most subclasses will have no need to \n    * override this method, relying solely on the {@link #initialValue} \n    * method to set the values of thread-locals. \n    * \n    * @param value the value to be stored in the current thread's copy of \n    *        this thread-local. \n    */  \n   public void set(T value) {  \n       Thread t = Thread.currentThread();  \n       ThreadLocalMap map = getMap(t);  \n       if (map != null)  \n           map.set(this, value);  \n       else  \n           createMap(t, value);  \n   }  \n

在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

\n

线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

\n

为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
/** \n * Get the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param  t the current thread \n * @return the map \n */  \nThreadLocalMap getMap(Thread t) {  \n    return t.threadLocals;  \n}  \n\n/** \n * Create the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param t the current thread \n * @param firstValue value for the initial entry of the map \n * @param map the map to store. \n */  \nvoid createMap(Thread t, T firstValue) {  \n    t.threadLocals = new ThreadLocalMap(this, firstValue);  \n}  \n

接下来再看一下ThreadLocal类中的get()方法:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
/** \n * Returns the value in the current thread's copy of this \n * thread-local variable.  If the variable has no value for the \n * current thread, it is first initialized to the value returned \n * by an invocation of the {@link #initialValue} method. \n * \n * @return the current thread's value of this thread-local \n */  \npublic T get() {  \n    Thread t = Thread.currentThread();  \n    ThreadLocalMap map = getMap(t);  \n    if (map != null) {  \n        ThreadLocalMap.Entry e = map.getEntry(this);  \n        if (e != null)  \n            return (T)e.value;  \n    }  \n    return setInitialValue();  \n}  \n

再来看setInitialValue()方法:

\n
/** \n    * Variant of set() to establish initialValue. Used instead \n    * of set() in case user has overridden the set() method. \n    * \n    * @return the initial value \n    */  \n   private T setInitialValue() {  \n       T value = initialValue();  \n       Thread t = Thread.currentThread();  \n       ThreadLocalMap map = getMap(t);  \n       if (map != null)  \n           map.set(this, value);  \n       else  \n           createMap(t, value);  \n       return value;  \n   }  \n

  获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

\n

  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。

\n

小结
  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
ConnectionManager.java

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.DriverManager;  \nimport java.sql.SQLException;  \n\npublic class ConnectionManager {  \n\n    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  \n        @Override  \n        protected Connection initialValue() {  \n            Connection conn = null;  \n            try {  \n                conn = DriverManager.getConnection(  \n                        "jdbc:mysql://localhost:3306/test", "username",  \n                        "password");  \n            } catch (SQLException e) {  \n                e.printStackTrace();  \n            }  \n            return conn;  \n        }  \n    };  \n\n    public static Connection getConnection() {  \n        return connectionHolder.get();  \n    }  \n\n    public static void setConnection(Connection conn) {  \n        connectionHolder.set(conn);  \n    }  \n}  \n
","site":{"data":{}},"excerpt":"

http://blog.csdn.net/lufeng20/article/details/24314381

\n

ThreadLocal是什么

  早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
  所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)设置当前线程的线程局部变量的值。
public Object get()该方法返回当前线程所对应的线程局部变量。
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
  值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

","more":"
package com.test;  \n\npublic class TestNum {  \n    // ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  \n    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  \n        public Integer initialValue() {  \n            return 0;  \n        }  \n    };  \n\n    // ②获取下一个序列值  \n    public int getNextNum() {  \n        seqNum.set(seqNum.get() + 1);  \n        return seqNum.get();  \n    }  \n\n    public static void main(String[] args) {  \n        TestNum sn = new TestNum();  \n        // ③ 3个线程共享sn,各自产生序列号  \n        TestClient t1 = new TestClient(sn);  \n        TestClient t2 = new TestClient(sn);  \n        TestClient t3 = new TestClient(sn);  \n        t1.start();  \n        t2.start();  \n        t3.start();  \n    }  \n\n    private static class TestClient extends Thread {  \n        private TestNum sn;  \n\n        public TestClient(TestNum sn) {  \n            this.sn = sn;  \n        }  \n\n        public void run() {  \n            for (int i = 0; i < 3; i++) {  \n                // ④每个线程打出3个序列值  \n                System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  \n                         + sn.getNextNum() + "]");  \n            }  \n        }  \n    }  \n}  \n

通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个TestNum实例。运行以上代码,在控制台上输出以下的结果:
thread[Thread-0] –> sn[1]
thread[Thread-1] –> sn[1]
thread[Thread-2] –> sn[1]
thread[Thread-1] –> sn[2]
thread[Thread-0] –> sn[2]
thread[Thread-1] –> sn[3]
thread[Thread-2] –> sn[2]
thread[Thread-0] –> sn[3]
thread[Thread-2] –> sn[3]
考察输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个TestNum实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本。

\n

Thread同步机制的比较
  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
  而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,代码清单 9 2就使用了JDK 5.0新的ThreadLocal版本。
  概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
  spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
  一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9‑2所示:
通通透透理解ThreadLocal
  同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。
  下面的实例能够体现Spring对有状态Bean的改造思路:
代码清单3 TestDao:非线程安全
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.SQLException;  \nimport java.sql.Statement;  \n\npublic class TestDao {  \n    private Connection conn;// ①一个非线程安全的变量  \n\n    public void addTopic() throws SQLException {  \n        Statement stat = conn.createStatement();// ②引用非线程安全变量  \n        // …  \n    }  \n}  \n

由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:
代码清单4 TestDao:线程安全

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.SQLException;  \nimport java.sql.Statement;  \n\npublic class TestDaoNew {  \n    // ①使用ThreadLocal保存Connection变量  \n    private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();  \n\n    public static Connection getConnection() {  \n        // ②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,  \n        // 并将其保存到线程本地变量中。  \n        if (connThreadLocal.get() == null) {  \n            Connection conn = getConnection();  \n            connThreadLocal.set(conn);  \n            return conn;  \n        } else {  \n            return connThreadLocal.get();// ③直接返回线程本地变量  \n        }  \n    }  \n\n    public void addTopic() throws SQLException {  \n        // ④从ThreadLocal中获取线程对应的Connection  \n        Statement stat = getConnection().createStatement();  \n    }  \n}  \n

不同的线程在使用TopicDao时,先判断connThreadLocal.get()是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建一个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使用就可以了。这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。
当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。

\n

ConnectionManager.java

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.DriverManager;  \nimport java.sql.SQLException;  \n\npublic class ConnectionManager {  \n\n    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  \n        @Override  \n        protected Connection initialValue() {  \n            Connection conn = null;  \n            try {  \n                conn = DriverManager.getConnection(  \n                        "jdbc:mysql://localhost:3306/test", "username",  \n                        "password");  \n            } catch (SQLException e) {  \n                e.printStackTrace();  \n            }  \n            return conn;  \n        }  \n    };  \n\n    public static Connection getConnection() {  \n        return connectionHolder.get();  \n    }  \n\n    public static void setConnection(Connection conn) {  \n        connectionHolder.set(conn);  \n    }  \n}  \n

java.lang.ThreadLocal的具体实现
那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
/** \n    * Sets the current thread's copy of this thread-local variable \n    * to the specified value.  Most subclasses will have no need to \n    * override this method, relying solely on the {@link #initialValue} \n    * method to set the values of thread-locals. \n    * \n    * @param value the value to be stored in the current thread's copy of \n    *        this thread-local. \n    */  \n   public void set(T value) {  \n       Thread t = Thread.currentThread();  \n       ThreadLocalMap map = getMap(t);  \n       if (map != null)  \n           map.set(this, value);  \n       else  \n           createMap(t, value);  \n   }  \n

在这个方法内部我们看到,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。

\n

线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

\n

为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
/** \n * Get the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param  t the current thread \n * @return the map \n */  \nThreadLocalMap getMap(Thread t) {  \n    return t.threadLocals;  \n}  \n\n/** \n * Create the map associated with a ThreadLocal. Overridden in \n * InheritableThreadLocal. \n * \n * @param t the current thread \n * @param firstValue value for the initial entry of the map \n * @param map the map to store. \n */  \nvoid createMap(Thread t, T firstValue) {  \n    t.threadLocals = new ThreadLocalMap(this, firstValue);  \n}  \n

接下来再看一下ThreadLocal类中的get()方法:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片

\n
/** \n * Returns the value in the current thread's copy of this \n * thread-local variable.  If the variable has no value for the \n * current thread, it is first initialized to the value returned \n * by an invocation of the {@link #initialValue} method. \n * \n * @return the current thread's value of this thread-local \n */  \npublic T get() {  \n    Thread t = Thread.currentThread();  \n    ThreadLocalMap map = getMap(t);  \n    if (map != null) {  \n        ThreadLocalMap.Entry e = map.getEntry(this);  \n        if (e != null)  \n            return (T)e.value;  \n    }  \n    return setInitialValue();  \n}  \n

再来看setInitialValue()方法:

\n
/** \n    * Variant of set() to establish initialValue. Used instead \n    * of set() in case user has overridden the set() method. \n    * \n    * @return the initial value \n    */  \n   private T setInitialValue() {  \n       T value = initialValue();  \n       Thread t = Thread.currentThread();  \n       ThreadLocalMap map = getMap(t);  \n       if (map != null)  \n           map.set(this, value);  \n       else  \n           createMap(t, value);  \n       return value;  \n   }  \n

  获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

\n

  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。

\n

小结
  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
ConnectionManager.java

\n
package com.test;  \n\nimport java.sql.Connection;  \nimport java.sql.DriverManager;  \nimport java.sql.SQLException;  \n\npublic class ConnectionManager {  \n\n    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  \n        @Override  \n        protected Connection initialValue() {  \n            Connection conn = null;  \n            try {  \n                conn = DriverManager.getConnection(  \n                        "jdbc:mysql://localhost:3306/test", "username",  \n                        "password");  \n            } catch (SQLException e) {  \n                e.printStackTrace();  \n            }  \n            return conn;  \n        }  \n    };  \n\n    public static Connection getConnection() {  \n        return connectionHolder.get();  \n    }  \n\n    public static void setConnection(Connection conn) {  \n        connectionHolder.set(conn);  \n    }  \n}  \n
"},{"title":"构建逻辑回归模型实例","comments":1,"description":"使用逻辑回归对信用卡欺诈数据进行训练分析预测","toc":true,"_content":"\n# 逻辑回归\n\n> 逻辑回归是应用非常广泛的一个分类机器学习算法,它将数据拟合到一个logit函数(或者叫做logistic函数)中,从而能够完成对事件发生的概率进行预测。\n\n# 构建逻辑回归模型步骤:\n* 导入数据\n* 预处理数据\n* 对不平衡的数据进行下采样(或者过采样)处理\n* 把处理之后的数据进行切分,切分为训训练集和测试集\n* 对训练集进行交叉验证,同时寻找最佳的正则化参数以减少过拟合\n* 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率\n* 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率\n* 修改阈值以获得更好的召回率和精确率\n\n\n## 1. 数据与任务\n\n### 信用卡欺诈数据\n\n```python\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n%matplotlib inline\n\ndata = pd.read_csv(\"creditcard.csv\")\ndata.head()\n```\n\n{% qnimg 20180307115819.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n### 要使用逻辑回归对数据进行建模 任务:二分类, 把数据分为有欺诈和无欺诈的两种数据\n\n## 2. 使用sklearn进行数据预处理\n\n> 公式为:(X-mean)/std 计算时对每个属性/每列分别进行。\n\nStandardization标准化:将特征数据的分布调整成标准正太分布,也叫高斯分布,也就是使得数据的均值维0,方差为1\n\n标准化的原因在于如果有些特征的方差过大,则会主导目标函数从而使参数估计器无法正确地去学习其他特征。\n\n标准化的过程为两步:去均值的中心化(均值变为0);方差的规模化(方差变为1)。\n\n在sklearn.preprocessing中提供了一个scale的方法,可以实现以上功能。如下面所示:\n\n\n```python\n x = np.array([[1., -1., 2.],\n [2., 0., 0.],\n [0., 1., -1.]])\n # 将每一列特征标准化为标准正太分布,注意,标准化是针对每一列而言的\n x_scale = preprocessing.scale(x)\n x_scale\n```\n\npreprocessing这个模块还提供了一个实用类StandarScaler,它可以在训练数据集上做了标准转换操作之后,把相同的转换应用到测试训练集中。\n可以对训练数据,测试数据应用相同的转换,以后有新的数据进来也可以直接调用,不用再重新把数据放在一起再计算一次了。\n\n```python\n # 调用fit方法,根据已有的训练数据创建一个标准化的转换器\n scaler = preprocessing.StandardScaler().fit(x)\n\n scaler\n \n StandardScaler(copy=True, with_mean=True, with_std=True)\n\n # 使用上面这个转换器去转换训练数据x,调用transform方法\n scaler.transform(x)\n```\n\n*StandardScaler()中可以传入两个参数:with_mean,with_std.这两个都是布尔型的参数,默认情况下都是true,但也可以自定义成false.即不要均值中心化或者不要方差规模化为1.*\n\n### 1. 处理数据 数据下采样\n#### 1.1 预处理数据,修改列\"Amount\"数据分布\n\n```python\n# 导入预处理sklearn中预处理模块的标准化模块\nfrom sklearn.preprocessing import StandardScaler\n\nif 'Amount' in data.columns:\n # 转化特征为新的特征\n data['normAount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1)) # reshape:改变数组的形状,参数为改变后的行列数 \n# fit_transform:对数据进行变换 矩阵旋转:-1表示自动识别 根据另一个矩阵列(行)数确定本行(列)数\n```\n\n### 1.2 数据处理,去除不需要的特征\n\n```python\n# 去掉两个没用的特征(列) axis=1表示对每一行去做这个操作,axis=0表示对每一列做相同的这个操作\nif ('Time') in data.columns:\n data = data.drop(['Time'], axis=1) \nif ('Amount') in data.columns:\n data = data.drop(['Amount'], axis=1) \nprint(data.columns, len(data.columns))\n\n# 1.2.1 数据图形化展示(1的数据太少索引看上去没有)\ncount_classes = pd.value_counts(data['Class'], sort=True).sort_index() # 画图显示按某列分类之后的数据数量比例\ncount_classes.plot(kind = 'bar') # bar:条形图\nplt.xlabel(\"Class\")\nplt.ylabel(\"Frequency\")\n\n# 1.2.2 原数据特征和分类\nX = data.loc[:, data.columns != \"Class\"]\ny = data.loc[:, data.columns == 'Class']\nprint (\"SHAPE\", X.shape, y.shape)\n\n# Class为0的数量远远大于1的数据,需要使数据个数相近 解决方案: 1.下采样(多的数据抽取部分) 2.过采样(少的数据生成更多)\n```\n\n{% qnimg 20180307120121.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n## 3.下采样\n\n> 把数据相对多的减少,可减少为和数据少的数量相同的数量\n\n### 1.3 区分正常数据和异常数据: 通过特征'Class'区分\n```python\n# 1.3.1 异常数据-信息\nnumber_records = data[data.Class == 1]\n# 1.3.2 异常数据个数\nnumber_records_fraud = len(number_records)\n# 1.3.3 异常数据索引\nfrand_indices = np.array(number_records.index)\nprint (\"异常样本索引 有{}个\".format(number_records_fraud), frand_indices[:10])\n\n# 1.3.4 正常数据-索引\nnormal_indices = data[data.Class == 0].index\nprint (\"正常样本索引 有{}个\".format(len(normal_indices)), normal_indices[-10:])\n\nprint (\">>>>>>>>>>>>>>>>>>>>所有数据 正常异常比 \", len(normal_indices), '\\t', number_records_fraud)\nprint(\"**************\")\n```\n\n### 1.4 下采样处理数据 把多的一方数据进行随机减少到与少的一方相同\n```python\n# 在所有的正常样本索引normal_indices中随机获取,随机选取number_records_fraud个\n# np.random.choice: 可以从一个int数字或1维array里随机选取内容,并将选取结果放入n维array中返回。\nrandom_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace=False)\nrandom_normal_indices = np.array(random_normal_indices)\nprint (\"'下采样'后有正常样本个数:\", len(random_normal_indices))\n```\n\n### 1.5 数据索引合并 (意思就是把新的正常数据和原来的异常数据进行拼接)\n```python\nunder_sample_indices = np.concatenate([frand_indices, random_normal_indices])\nprint (\"合并后有样本个数:\", len(under_sample_indices))\nunder_sample_data = data.iloc[under_sample_indices, :]\nprint (\"合并后样本:\", under_sample_data[:1])\n\nprint (\">>>>>>>>>>>>>>>>>>>>下采样数据 正常异常比 \", len(under_sample_data[under_sample_data == 0]), '\\t', len(under_sample_data[under_sample_data == 1]))\n```\n\n### 1.6 获取合并数据中的feature(特征)和label(分类)\n```python\nX_undersample = under_sample_data.loc[:, under_sample_data.columns != 'Class']\ny_undersample = under_sample_data.loc[:, under_sample_data.columns == 'Class']\n\nprint (X_undersample.shape, y_undersample.shape)\nprint (len(under_sample_data[under_sample_data[\"Class\"] == 1]), len(under_sample_data[under_sample_data[\"Class\"] == 0]))\n```\n\n{% qnimg 20180307120308.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n### 2. 切分数据为训练和测试\n```python\n# 切分原始数据 取数据集中80%的数据作为训练集(建立model) 其他20%的为测试集(测试model)\nfrom sklearn.cross_validation import train_test_split\n\nprint (X.shape, y.shape)\n## 2.1.对原始数据进行切分 (最终需要使用原数据集中的测试数据进行测试)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) # test_size测试集所占比例 random_state切分之前进行乱序\nprint (\"1.训练集数据大小\", X_train.shape)\nprint (\"2.测试集数据大小\", X_test.shape)\nprint (len(X_train) + len(X_test), len(y_train), len(y_test), \"\\n\")\n\n## 2.2.对下采样数据进行切分\nX_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split(X_undersample, y_undersample, test_size=0.2, random_state=0) # test_size测试集大小 random_state切分之前进行乱序\nprint (\"3.训练集数据大小\", X_train_undersample.shape)\nprint (\"4.测试集数据大小\", X_test_undersample.shape)\nprint (len(X_train_undersample) + len(X_test_undersample), len(y_train_undersample), len(y_test_undersample), \"\\n\")\n\n# 切分训练集 把训练集平均切分为三分然后进行交叉验证 (三组数据分别进行建模和验证)\n```\n\n{% qnimg 20180307120401.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n## 评估标准:召回率(recall)\n\n*不适用准确率,因为准确率不能正确的得到所求的,是没用的*\n\n### 模型评估表:\n\n | 相关(Relevant),正类 | 不相关(NonRelevant),负类\n- | :-: | -: \n被检测到(Retrieved) | true positives (TP) | false positives (FP)\n未被检测到(Retrieved) | false negatives (FN) | true negatives (TN)\n\n### 一些术语:\n\n* TP:True Positive,即正确预测出的正样本个数\n* FP:False Positive,即错误预测出的正样本个数(本来是负样本,被我们预测成了正样本)\n* TN:True Negative,即正确预测出的负样本个数\n* FN:False Negative,即错误预测出的负样本个数(本来是正样本,被我们预测成了负样本)\n\n### 分类器性能评价指标\n由以上四个指标,可以进一步衍生出其他三个常用的评价分类器性能的指标\n\n* Precision(精确率):TP÷(TP+FP)TP÷(TP+FP),分类器预测出的正样本中,真实正样本的比例\n* Recall(召回率):TP÷(TP+FN)TP÷(TP+FN),在所有真实正样本中,分类器中能找到多少\n* Accuracy(准确率):(TP+TN)÷(TP+NP+TN+FN)(TP+TN)÷(TP+NP+TN+FN),分类器对整体的判断能力,即正确预测的比例\n\n> 过拟合: 数据在训练集表现很好 在测试集表现很差\n\n```python\nfrom sklearn.linear_model import LogisticRegression # 逻辑回归\n# 注意这里导入的 不是from sklearn.model_selection import KFold\nfrom sklearn.cross_validation import KFold # 交叉验证 # cross_val_score\nfrom sklearn.metrics import confusion_matrix, recall_score, classification_report # 混淆矩阵 \n```\n\n### 3. 通过多次循环交叉验证 确定正则化参数 random_state:随机种子数\n```python\ndef printing_Kfold_scores(x_train_data, y_train_data):\n # KFold:切分数据集 (这里切分为5部分) shuffle:是否每次都\"洗牌\"(Falses时,其效果等同于random_state等于整数,每次划分的结果相同)\n fold = KFold(len(y_train_data), 5, shuffle=False) \n print (type(fold), len(y_train_data), len(fold)) # 长度是5\n \n # 正则化惩罚项(正则化参数) 预设了多个惩罚值,具体使用哪个需要尝试 列举了5个\n c_param_range = [0.01, 0.1, 1, 10, 100]\n \n # 新建DataFrame类型的数据用来存放不同正则化之后的结果\n results_table = pd.DataFrame(index = range(len(c_param_range)), columns = ['C_parameter', 'Mean recall score'])\n results_table['C_parameter'] = c_param_range\n\n # 先按照正则化参数进行循环以确定最好的参数 然后对每个逻辑回归进行交叉验证以获得最好的逻辑回归函数\n # 循环正则化参数 获取最好的c参数\n for index, c_param in enumerate(c_param_range):\n print (\">>>>>>>>>>>>>>>>>>>>>>>>>>\")\n print (\"C_parameter \", c_param)\n \n recall_accs = []\n # 循环进行交叉验证 \n # 每次循环次数为数据切分的大小,切分为n块就交叉验证n次,每次都是区其中n-1块为训练集1块为验证集\n # start=1:开始索引为1\n # iteration为索引 indices为划分好的数据:其中有n-1数据大小的训练集以及1数据代销的验证集\n # 循环中集合每次都不一样,所有的数据都会当一次验证集:例如 三个数据[1,2,3],循环使得数据分别为训练和验证每次为:[[1],[2, 3]], [[2],[1, 3]], [[3],[1, 2]]\n for iteration, indices in enumerate(fold, start=1):\n # 这里并不是用fold直接划分训练集数据, 而是把索引进行1:5的划分, 然后按照索引获取数据中的对应的数据\n print (iteration, len(indices[0]), len(indices[1]))\n \n # 建立逻辑回归模型\n lr = LogisticRegression(C = c_param, penalty = 'l1') # C:正则化参数; penalty:惩罚项:使用L1正则化(惩罚) ‘l1’ or ‘l2’(默认: ‘l2’)\n # 在调参时如果我们主要的目的只是为了解决过拟合,一般penalty选择L2正则化就够了。\n # 但是如果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。\n # 另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化。\n # print (\"LR-逻辑回归表达式---\", lr)\n \n # 训练 参数一:训练数据特征(feature) 参数二:训练数据分类(label)\n lr.fit(x_train_data.iloc[indices[0],:], y_train_data.iloc[indices[0],:].values.ravel())\n \n # 预测\n y_pred_undersample = lr.predict(x_train_data.iloc[indices[1], :].values)\n \n # 计算召回率 召回率 =提取出的正确信息条数 /样本中的信息条数。通俗地说,就是所有准确的条目有多少被检索出来了。\n # 参数: 1.真实数据集 2.预测数据集\n recall_acc = recall_score(y_train_data.iloc[indices[1],:].values, y_pred_undersample)\n recall_accs.append(recall_acc)\n print (len(indices), \"Iteration \", iteration, \": recall score = \", recall_acc)\n \n # 求每个惩罚值经过交叉验证之后平均召回率\n results_table.loc[index, 'Mean recall score'] = np.mean(recall_accs)\n\n print ('\\nMean recall score ', np.mean(recall_accs), '\\n')\n \n print (results_table)\n \n best_c = results_table.loc[results_table['Mean recall score'].idxmax()]['C_parameter']\n print (\"finally-------best is--------> \", best_c)\n \n return best_c\n\nbest_c = printing_Kfold_scores(X_train_undersample, y_train_undersample)\n```\n\n{% qnimg 20180307120616.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307120702.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307120717.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n#### ndarray数据格式化: set_printoptions\n\n> set_printoptions(precision=None, \n threshold=None, \n edgeitems=None,\n linewidth=None, \n suppress=None,\n nanstr=None,\n infstr=None,\n formatter=None)\n\n* precision:输出结果保留精度的位数 (num)\n* threshold:array数量的个数在小于threshold的时候不会被折叠 (num)\n* edgeitems:在array已经被折叠后,开头和结尾都会显示edgeitems个数 (num)\n* formatter:这个很有意思,像python3里面str.format(),就是可以对你的输出进行自定义的格式化 其他的暂时没用到\n\n### 4. 使用最好的正则化参数 构建逻辑回归模型并进行测试\n```python\n# 构建逻辑回归模型\nlr = LogisticRegression(C = best_c, penalty='l1')\n# 训练回归模型\nlr.fit(X_train_undersample, y_train_undersample.values.ravel())\n# 使用模型进行测试\ny_pred_undersample = lr.predict(X_test_undersample.values)\n# y_pred_undersample为预测(分类)值, y_test_undersample为真实测试集的(分类)值\n\nprint (type(y_pred_undersample), len(y_pred_undersample), \"\\n\")\n\n# 打印和绘制混淆矩阵\nimport itertools\ndef plot_confusion_matrix(cm, classes, title='Confussion matrix', cmap=plt.cm.Blues):\n #设置显示混淆矩阵\n plt.imshow(cm, interpolation='nearest', cmap=cmap)\n plt.title(title)\n plt.colorbar()\n \n # 设置坐标数\n tick_marks = np.arange(len(classes))\n plt.xticks(tick_marks, classes, rotation=0)\n plt.yticks(tick_marks, classes)\n \n thresh = cm.max() / 2\n \n # itertools.product可进行多层次循环 传入参数个数(n)和索引个数相同 可循环n^2次\n # 设置每个方块中的文字 \n for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n # print (j, i, cm[i, j])\n # 因为i表示横坐标的位置, j表示纵坐标的位置 所以需要把i和j交换位置\n plt.text(j, i, cm[i, j], horizontalalignment=\"center\", color=\"white\" if cm[i, j] > thresh else \"black\")\n \n plt.tight_layout()\n # 设置坐标文字\n plt.ylabel(\"True label\") # 真实数据 \n plt.xlabel(\"Predicted label\") # 预测数据 1表示正例 0表示负例\n\n# 画混淆矩阵图 参数: 1.y_true, 2.y_pred\ncnf_matrix = confusion_matrix(y_test_undersample, y_pred_undersample)\nnp.set_printoptions(precision=2)\n\nprint (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n\nclass_names = [0, 1]\nplt.figure()\nplot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')\nplt.show()\n\n# 由图可见, 召回率为 85 / (85 + 6) = 93.41%\n# 精确率为 (85) / (85 + 9) = 90.43%\n# 准确率为 (85 + 97) / (85 + 9 + 6 + 97) = 92.39%\n\n# 以上计算都是基于下采样数据集的,还需要在原数据的测试集上进行测试操作 (与上面同理)\n```\n\n{% qnimg 20180307120847.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n### 4. 使用最好的正则化参数 构建逻辑回归模型并进行测试 (使用原始数据的测试集和训练集)\n```python\n# 在原数据的测试集上进行测试操作\nlr = LogisticRegression(C = best_c, penalty='l1')\nlr.fit(X_train, y_train.values.ravel())\ny_pred = lr.predict(X_test.values)\n# y_pred为预测(分类)值, y_test为真实测试集的(分类)值\n\n\ncnf_matrix = confusion_matrix(y_test, y_pred)\nnp.set_printoptions(precision=2)\n\nprint (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n\nclass_names = [0, 1]\nplt.figure()\nplot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')\nplt.show()\n```\n\n### 5. 修改阈值以获取最好的逻辑回归模型\n```python\n# 阈值: 默认使用sigma函数默认值:0.5, 意思是当预测概率大于0.5表示True,概率小鱼0.5表示False\n\nlr = LogisticRegression(C = best_c, penalty='l1')\n# 训练\nlr.fit(X_train, y_train.values.ravel())\n# 预测 这里是预测概率值 每个数据的预测包含两个值,对于二分类问题,也就是被判断为0的概率和被判断为1的概率\ny_pred_undersample_proba = lr.predict_proba(X_test_undersample.values) # 预测概率值而不是类别值\n\n# 可能的阈值\nthresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n\nplt.figure(figsize=(10, 10)) # 画图域\n\nfor index, i in enumerate(thresholds):\n # 预测概率\n y_test_predictions_high_recall = y_pred_undersample_proba[:, 1] > i\n \n plt.subplot(3, 3, index + 1)\n \n cnf_matrix = confusion_matrix(y_test_undersample, y_test_predictions_high_recall)\n np.set_printoptions(precision=2)\n \n print (i, \"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n \n class_names = [0, 1]\n plot_confusion_matrix(cnf_matrix, classes=class_names, title=\"Threshold >= %s\" %i)\n \n## 随着阈值上升 召回率不断变化 其中本来是1的被误检测为0的越来越多 可见 要选取最合适的阈值以达到召回率最高\n```\n\n{% qnimg 20180307120937.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307121001.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307121022.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n## 过采样\n\n> 把数据相对少的增加,可增加为和数据多的数量相同的数量 (生成)\n\n```python\nimport pandas as pd\nfrom imblearn.over_sampling import SMOTE\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.metrics import confusion_matrix\nfrom sklearn.model_selection import train_test_split\n\ncredit_cards = pd.read_csv(\"creditcard.csv\")\ncolumns = credit_cards.columns\n\nfeatures_columns = columns.delete(len(columns) - 1) #删除最后一列数据\nprint (features_columns)\n\nfeatures = credit_cards[features_columns]\nlabels = credit_cards['Class']\n\nprint (\"原始的数据个数\", (credit_cards[credit_cards['Class'] == 0]).shape, (credit_cards[credit_cards['Class'] == 1]).shape)\n\n```\n\n\n```python\nfeatures_train, features_test, labels_train, labels_test = train_test_split(features, labels, test_size=0.2, random_state=0)\nprint (features_train.shape, features_test.shape, labels_train.shape, labels_test.shape)\n```\n{% qnimg 20180307121128.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n```python \noversampler = SMOTE(random_state = 0) # SMOTE随机生成数据 生成只能是训练集生成数据, 而测试集不生成\n# 只生成训练集数据 使得Class为1和为0的数量相同 返回训练集的特征和分类\nos_features, os_labels = oversampler.fit_sample(features_train, labels_train)\n\nprint (\"可见 的确生成了新的数据,补充了异常的数据 \", len(os_labels[os_labels[:] == 1]), len(os_labels[os_labels[:] == 0]))\n\nprint ((os_features).shape, len(os_features[os_features == 1]), len(os_features[os_features == 0]), \n (os_labels).shape, len(os_labels[os_labels == 1]), len(os_labels[os_labels == 0]))\n```\n{% qnimg 20180307121154.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n```python\nos_features = pd.DataFrame(os_features)\nos_labels = pd.DataFrame(os_labels)\n# 获取最佳参数\nbest_c = printing_Kfold_scores(os_features, os_labels)\n\n# plot_confusion_matrix\n```\n\n```python\nlr = LogisticRegression(C = best_c, penalty='l1')\n# 训练 使用生成的数据\nlr.fit(os_features, os_labels.values.ravel())\n# 使用真实数据测试\ny_pred = lr.predict(features_test.values)\n\n# 打印和绘制混淆矩阵\ncnf_matrix = confusion_matrix(labels_test, y_pred)\nnp.set_printoptions(precision=2)\n\nprint (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n\nclass_names = [0, 1]\nplt.figure()\nplot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')\n\nplt.show()\n```\n> 个人博客 欢迎来访: http://zj2626.github.io","source":"_posts/2018030701.md","raw":"---\ntitle: 构建逻辑回归模型实例\n\ncomments: true \n\ntags: \n - 逻辑回归\n - python\n - 机器学习\n\ncategories: \n - 机器学习\n\ndescription: 使用逻辑回归对信用卡欺诈数据进行训练分析预测\n\ntoc: true\n \n---\n\n# 逻辑回归\n\n> 逻辑回归是应用非常广泛的一个分类机器学习算法,它将数据拟合到一个logit函数(或者叫做logistic函数)中,从而能够完成对事件发生的概率进行预测。\n\n# 构建逻辑回归模型步骤:\n* 导入数据\n* 预处理数据\n* 对不平衡的数据进行下采样(或者过采样)处理\n* 把处理之后的数据进行切分,切分为训训练集和测试集\n* 对训练集进行交叉验证,同时寻找最佳的正则化参数以减少过拟合\n* 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率\n* 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率\n* 修改阈值以获得更好的召回率和精确率\n\n\n## 1. 数据与任务\n\n### 信用卡欺诈数据\n\n```python\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n%matplotlib inline\n\ndata = pd.read_csv(\"creditcard.csv\")\ndata.head()\n```\n\n{% qnimg 20180307115819.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n### 要使用逻辑回归对数据进行建模 任务:二分类, 把数据分为有欺诈和无欺诈的两种数据\n\n## 2. 使用sklearn进行数据预处理\n\n> 公式为:(X-mean)/std 计算时对每个属性/每列分别进行。\n\nStandardization标准化:将特征数据的分布调整成标准正太分布,也叫高斯分布,也就是使得数据的均值维0,方差为1\n\n标准化的原因在于如果有些特征的方差过大,则会主导目标函数从而使参数估计器无法正确地去学习其他特征。\n\n标准化的过程为两步:去均值的中心化(均值变为0);方差的规模化(方差变为1)。\n\n在sklearn.preprocessing中提供了一个scale的方法,可以实现以上功能。如下面所示:\n\n\n```python\n x = np.array([[1., -1., 2.],\n [2., 0., 0.],\n [0., 1., -1.]])\n # 将每一列特征标准化为标准正太分布,注意,标准化是针对每一列而言的\n x_scale = preprocessing.scale(x)\n x_scale\n```\n\npreprocessing这个模块还提供了一个实用类StandarScaler,它可以在训练数据集上做了标准转换操作之后,把相同的转换应用到测试训练集中。\n可以对训练数据,测试数据应用相同的转换,以后有新的数据进来也可以直接调用,不用再重新把数据放在一起再计算一次了。\n\n```python\n # 调用fit方法,根据已有的训练数据创建一个标准化的转换器\n scaler = preprocessing.StandardScaler().fit(x)\n\n scaler\n \n StandardScaler(copy=True, with_mean=True, with_std=True)\n\n # 使用上面这个转换器去转换训练数据x,调用transform方法\n scaler.transform(x)\n```\n\n*StandardScaler()中可以传入两个参数:with_mean,with_std.这两个都是布尔型的参数,默认情况下都是true,但也可以自定义成false.即不要均值中心化或者不要方差规模化为1.*\n\n### 1. 处理数据 数据下采样\n#### 1.1 预处理数据,修改列\"Amount\"数据分布\n\n```python\n# 导入预处理sklearn中预处理模块的标准化模块\nfrom sklearn.preprocessing import StandardScaler\n\nif 'Amount' in data.columns:\n # 转化特征为新的特征\n data['normAount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1)) # reshape:改变数组的形状,参数为改变后的行列数 \n# fit_transform:对数据进行变换 矩阵旋转:-1表示自动识别 根据另一个矩阵列(行)数确定本行(列)数\n```\n\n### 1.2 数据处理,去除不需要的特征\n\n```python\n# 去掉两个没用的特征(列) axis=1表示对每一行去做这个操作,axis=0表示对每一列做相同的这个操作\nif ('Time') in data.columns:\n data = data.drop(['Time'], axis=1) \nif ('Amount') in data.columns:\n data = data.drop(['Amount'], axis=1) \nprint(data.columns, len(data.columns))\n\n# 1.2.1 数据图形化展示(1的数据太少索引看上去没有)\ncount_classes = pd.value_counts(data['Class'], sort=True).sort_index() # 画图显示按某列分类之后的数据数量比例\ncount_classes.plot(kind = 'bar') # bar:条形图\nplt.xlabel(\"Class\")\nplt.ylabel(\"Frequency\")\n\n# 1.2.2 原数据特征和分类\nX = data.loc[:, data.columns != \"Class\"]\ny = data.loc[:, data.columns == 'Class']\nprint (\"SHAPE\", X.shape, y.shape)\n\n# Class为0的数量远远大于1的数据,需要使数据个数相近 解决方案: 1.下采样(多的数据抽取部分) 2.过采样(少的数据生成更多)\n```\n\n{% qnimg 20180307120121.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n## 3.下采样\n\n> 把数据相对多的减少,可减少为和数据少的数量相同的数量\n\n### 1.3 区分正常数据和异常数据: 通过特征'Class'区分\n```python\n# 1.3.1 异常数据-信息\nnumber_records = data[data.Class == 1]\n# 1.3.2 异常数据个数\nnumber_records_fraud = len(number_records)\n# 1.3.3 异常数据索引\nfrand_indices = np.array(number_records.index)\nprint (\"异常样本索引 有{}个\".format(number_records_fraud), frand_indices[:10])\n\n# 1.3.4 正常数据-索引\nnormal_indices = data[data.Class == 0].index\nprint (\"正常样本索引 有{}个\".format(len(normal_indices)), normal_indices[-10:])\n\nprint (\">>>>>>>>>>>>>>>>>>>>所有数据 正常异常比 \", len(normal_indices), '\\t', number_records_fraud)\nprint(\"**************\")\n```\n\n### 1.4 下采样处理数据 把多的一方数据进行随机减少到与少的一方相同\n```python\n# 在所有的正常样本索引normal_indices中随机获取,随机选取number_records_fraud个\n# np.random.choice: 可以从一个int数字或1维array里随机选取内容,并将选取结果放入n维array中返回。\nrandom_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace=False)\nrandom_normal_indices = np.array(random_normal_indices)\nprint (\"'下采样'后有正常样本个数:\", len(random_normal_indices))\n```\n\n### 1.5 数据索引合并 (意思就是把新的正常数据和原来的异常数据进行拼接)\n```python\nunder_sample_indices = np.concatenate([frand_indices, random_normal_indices])\nprint (\"合并后有样本个数:\", len(under_sample_indices))\nunder_sample_data = data.iloc[under_sample_indices, :]\nprint (\"合并后样本:\", under_sample_data[:1])\n\nprint (\">>>>>>>>>>>>>>>>>>>>下采样数据 正常异常比 \", len(under_sample_data[under_sample_data == 0]), '\\t', len(under_sample_data[under_sample_data == 1]))\n```\n\n### 1.6 获取合并数据中的feature(特征)和label(分类)\n```python\nX_undersample = under_sample_data.loc[:, under_sample_data.columns != 'Class']\ny_undersample = under_sample_data.loc[:, under_sample_data.columns == 'Class']\n\nprint (X_undersample.shape, y_undersample.shape)\nprint (len(under_sample_data[under_sample_data[\"Class\"] == 1]), len(under_sample_data[under_sample_data[\"Class\"] == 0]))\n```\n\n{% qnimg 20180307120308.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n### 2. 切分数据为训练和测试\n```python\n# 切分原始数据 取数据集中80%的数据作为训练集(建立model) 其他20%的为测试集(测试model)\nfrom sklearn.cross_validation import train_test_split\n\nprint (X.shape, y.shape)\n## 2.1.对原始数据进行切分 (最终需要使用原数据集中的测试数据进行测试)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) # test_size测试集所占比例 random_state切分之前进行乱序\nprint (\"1.训练集数据大小\", X_train.shape)\nprint (\"2.测试集数据大小\", X_test.shape)\nprint (len(X_train) + len(X_test), len(y_train), len(y_test), \"\\n\")\n\n## 2.2.对下采样数据进行切分\nX_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split(X_undersample, y_undersample, test_size=0.2, random_state=0) # test_size测试集大小 random_state切分之前进行乱序\nprint (\"3.训练集数据大小\", X_train_undersample.shape)\nprint (\"4.测试集数据大小\", X_test_undersample.shape)\nprint (len(X_train_undersample) + len(X_test_undersample), len(y_train_undersample), len(y_test_undersample), \"\\n\")\n\n# 切分训练集 把训练集平均切分为三分然后进行交叉验证 (三组数据分别进行建模和验证)\n```\n\n{% qnimg 20180307120401.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n## 评估标准:召回率(recall)\n\n*不适用准确率,因为准确率不能正确的得到所求的,是没用的*\n\n### 模型评估表:\n\n | 相关(Relevant),正类 | 不相关(NonRelevant),负类\n- | :-: | -: \n被检测到(Retrieved) | true positives (TP) | false positives (FP)\n未被检测到(Retrieved) | false negatives (FN) | true negatives (TN)\n\n### 一些术语:\n\n* TP:True Positive,即正确预测出的正样本个数\n* FP:False Positive,即错误预测出的正样本个数(本来是负样本,被我们预测成了正样本)\n* TN:True Negative,即正确预测出的负样本个数\n* FN:False Negative,即错误预测出的负样本个数(本来是正样本,被我们预测成了负样本)\n\n### 分类器性能评价指标\n由以上四个指标,可以进一步衍生出其他三个常用的评价分类器性能的指标\n\n* Precision(精确率):TP÷(TP+FP)TP÷(TP+FP),分类器预测出的正样本中,真实正样本的比例\n* Recall(召回率):TP÷(TP+FN)TP÷(TP+FN),在所有真实正样本中,分类器中能找到多少\n* Accuracy(准确率):(TP+TN)÷(TP+NP+TN+FN)(TP+TN)÷(TP+NP+TN+FN),分类器对整体的判断能力,即正确预测的比例\n\n> 过拟合: 数据在训练集表现很好 在测试集表现很差\n\n```python\nfrom sklearn.linear_model import LogisticRegression # 逻辑回归\n# 注意这里导入的 不是from sklearn.model_selection import KFold\nfrom sklearn.cross_validation import KFold # 交叉验证 # cross_val_score\nfrom sklearn.metrics import confusion_matrix, recall_score, classification_report # 混淆矩阵 \n```\n\n### 3. 通过多次循环交叉验证 确定正则化参数 random_state:随机种子数\n```python\ndef printing_Kfold_scores(x_train_data, y_train_data):\n # KFold:切分数据集 (这里切分为5部分) shuffle:是否每次都\"洗牌\"(Falses时,其效果等同于random_state等于整数,每次划分的结果相同)\n fold = KFold(len(y_train_data), 5, shuffle=False) \n print (type(fold), len(y_train_data), len(fold)) # 长度是5\n \n # 正则化惩罚项(正则化参数) 预设了多个惩罚值,具体使用哪个需要尝试 列举了5个\n c_param_range = [0.01, 0.1, 1, 10, 100]\n \n # 新建DataFrame类型的数据用来存放不同正则化之后的结果\n results_table = pd.DataFrame(index = range(len(c_param_range)), columns = ['C_parameter', 'Mean recall score'])\n results_table['C_parameter'] = c_param_range\n\n # 先按照正则化参数进行循环以确定最好的参数 然后对每个逻辑回归进行交叉验证以获得最好的逻辑回归函数\n # 循环正则化参数 获取最好的c参数\n for index, c_param in enumerate(c_param_range):\n print (\">>>>>>>>>>>>>>>>>>>>>>>>>>\")\n print (\"C_parameter \", c_param)\n \n recall_accs = []\n # 循环进行交叉验证 \n # 每次循环次数为数据切分的大小,切分为n块就交叉验证n次,每次都是区其中n-1块为训练集1块为验证集\n # start=1:开始索引为1\n # iteration为索引 indices为划分好的数据:其中有n-1数据大小的训练集以及1数据代销的验证集\n # 循环中集合每次都不一样,所有的数据都会当一次验证集:例如 三个数据[1,2,3],循环使得数据分别为训练和验证每次为:[[1],[2, 3]], [[2],[1, 3]], [[3],[1, 2]]\n for iteration, indices in enumerate(fold, start=1):\n # 这里并不是用fold直接划分训练集数据, 而是把索引进行1:5的划分, 然后按照索引获取数据中的对应的数据\n print (iteration, len(indices[0]), len(indices[1]))\n \n # 建立逻辑回归模型\n lr = LogisticRegression(C = c_param, penalty = 'l1') # C:正则化参数; penalty:惩罚项:使用L1正则化(惩罚) ‘l1’ or ‘l2’(默认: ‘l2’)\n # 在调参时如果我们主要的目的只是为了解决过拟合,一般penalty选择L2正则化就够了。\n # 但是如果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。\n # 另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化。\n # print (\"LR-逻辑回归表达式---\", lr)\n \n # 训练 参数一:训练数据特征(feature) 参数二:训练数据分类(label)\n lr.fit(x_train_data.iloc[indices[0],:], y_train_data.iloc[indices[0],:].values.ravel())\n \n # 预测\n y_pred_undersample = lr.predict(x_train_data.iloc[indices[1], :].values)\n \n # 计算召回率 召回率 =提取出的正确信息条数 /样本中的信息条数。通俗地说,就是所有准确的条目有多少被检索出来了。\n # 参数: 1.真实数据集 2.预测数据集\n recall_acc = recall_score(y_train_data.iloc[indices[1],:].values, y_pred_undersample)\n recall_accs.append(recall_acc)\n print (len(indices), \"Iteration \", iteration, \": recall score = \", recall_acc)\n \n # 求每个惩罚值经过交叉验证之后平均召回率\n results_table.loc[index, 'Mean recall score'] = np.mean(recall_accs)\n\n print ('\\nMean recall score ', np.mean(recall_accs), '\\n')\n \n print (results_table)\n \n best_c = results_table.loc[results_table['Mean recall score'].idxmax()]['C_parameter']\n print (\"finally-------best is--------> \", best_c)\n \n return best_c\n\nbest_c = printing_Kfold_scores(X_train_undersample, y_train_undersample)\n```\n\n{% qnimg 20180307120616.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307120702.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307120717.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n#### ndarray数据格式化: set_printoptions\n\n> set_printoptions(precision=None, \n threshold=None, \n edgeitems=None,\n linewidth=None, \n suppress=None,\n nanstr=None,\n infstr=None,\n formatter=None)\n\n* precision:输出结果保留精度的位数 (num)\n* threshold:array数量的个数在小于threshold的时候不会被折叠 (num)\n* edgeitems:在array已经被折叠后,开头和结尾都会显示edgeitems个数 (num)\n* formatter:这个很有意思,像python3里面str.format(),就是可以对你的输出进行自定义的格式化 其他的暂时没用到\n\n### 4. 使用最好的正则化参数 构建逻辑回归模型并进行测试\n```python\n# 构建逻辑回归模型\nlr = LogisticRegression(C = best_c, penalty='l1')\n# 训练回归模型\nlr.fit(X_train_undersample, y_train_undersample.values.ravel())\n# 使用模型进行测试\ny_pred_undersample = lr.predict(X_test_undersample.values)\n# y_pred_undersample为预测(分类)值, y_test_undersample为真实测试集的(分类)值\n\nprint (type(y_pred_undersample), len(y_pred_undersample), \"\\n\")\n\n# 打印和绘制混淆矩阵\nimport itertools\ndef plot_confusion_matrix(cm, classes, title='Confussion matrix', cmap=plt.cm.Blues):\n #设置显示混淆矩阵\n plt.imshow(cm, interpolation='nearest', cmap=cmap)\n plt.title(title)\n plt.colorbar()\n \n # 设置坐标数\n tick_marks = np.arange(len(classes))\n plt.xticks(tick_marks, classes, rotation=0)\n plt.yticks(tick_marks, classes)\n \n thresh = cm.max() / 2\n \n # itertools.product可进行多层次循环 传入参数个数(n)和索引个数相同 可循环n^2次\n # 设置每个方块中的文字 \n for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n # print (j, i, cm[i, j])\n # 因为i表示横坐标的位置, j表示纵坐标的位置 所以需要把i和j交换位置\n plt.text(j, i, cm[i, j], horizontalalignment=\"center\", color=\"white\" if cm[i, j] > thresh else \"black\")\n \n plt.tight_layout()\n # 设置坐标文字\n plt.ylabel(\"True label\") # 真实数据 \n plt.xlabel(\"Predicted label\") # 预测数据 1表示正例 0表示负例\n\n# 画混淆矩阵图 参数: 1.y_true, 2.y_pred\ncnf_matrix = confusion_matrix(y_test_undersample, y_pred_undersample)\nnp.set_printoptions(precision=2)\n\nprint (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n\nclass_names = [0, 1]\nplt.figure()\nplot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')\nplt.show()\n\n# 由图可见, 召回率为 85 / (85 + 6) = 93.41%\n# 精确率为 (85) / (85 + 9) = 90.43%\n# 准确率为 (85 + 97) / (85 + 9 + 6 + 97) = 92.39%\n\n# 以上计算都是基于下采样数据集的,还需要在原数据的测试集上进行测试操作 (与上面同理)\n```\n\n{% qnimg 20180307120847.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n### 4. 使用最好的正则化参数 构建逻辑回归模型并进行测试 (使用原始数据的测试集和训练集)\n```python\n# 在原数据的测试集上进行测试操作\nlr = LogisticRegression(C = best_c, penalty='l1')\nlr.fit(X_train, y_train.values.ravel())\ny_pred = lr.predict(X_test.values)\n# y_pred为预测(分类)值, y_test为真实测试集的(分类)值\n\n\ncnf_matrix = confusion_matrix(y_test, y_pred)\nnp.set_printoptions(precision=2)\n\nprint (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n\nclass_names = [0, 1]\nplt.figure()\nplot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')\nplt.show()\n```\n\n### 5. 修改阈值以获取最好的逻辑回归模型\n```python\n# 阈值: 默认使用sigma函数默认值:0.5, 意思是当预测概率大于0.5表示True,概率小鱼0.5表示False\n\nlr = LogisticRegression(C = best_c, penalty='l1')\n# 训练\nlr.fit(X_train, y_train.values.ravel())\n# 预测 这里是预测概率值 每个数据的预测包含两个值,对于二分类问题,也就是被判断为0的概率和被判断为1的概率\ny_pred_undersample_proba = lr.predict_proba(X_test_undersample.values) # 预测概率值而不是类别值\n\n# 可能的阈值\nthresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n\nplt.figure(figsize=(10, 10)) # 画图域\n\nfor index, i in enumerate(thresholds):\n # 预测概率\n y_test_predictions_high_recall = y_pred_undersample_proba[:, 1] > i\n \n plt.subplot(3, 3, index + 1)\n \n cnf_matrix = confusion_matrix(y_test_undersample, y_test_predictions_high_recall)\n np.set_printoptions(precision=2)\n \n print (i, \"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n \n class_names = [0, 1]\n plot_confusion_matrix(cnf_matrix, classes=class_names, title=\"Threshold >= %s\" %i)\n \n## 随着阈值上升 召回率不断变化 其中本来是1的被误检测为0的越来越多 可见 要选取最合适的阈值以达到召回率最高\n```\n\n{% qnimg 20180307120937.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307121001.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n{% qnimg 20180307121022.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n## 过采样\n\n> 把数据相对少的增加,可增加为和数据多的数量相同的数量 (生成)\n\n```python\nimport pandas as pd\nfrom imblearn.over_sampling import SMOTE\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.metrics import confusion_matrix\nfrom sklearn.model_selection import train_test_split\n\ncredit_cards = pd.read_csv(\"creditcard.csv\")\ncolumns = credit_cards.columns\n\nfeatures_columns = columns.delete(len(columns) - 1) #删除最后一列数据\nprint (features_columns)\n\nfeatures = credit_cards[features_columns]\nlabels = credit_cards['Class']\n\nprint (\"原始的数据个数\", (credit_cards[credit_cards['Class'] == 0]).shape, (credit_cards[credit_cards['Class'] == 1]).shape)\n\n```\n\n\n```python\nfeatures_train, features_test, labels_train, labels_test = train_test_split(features, labels, test_size=0.2, random_state=0)\nprint (features_train.shape, features_test.shape, labels_train.shape, labels_test.shape)\n```\n{% qnimg 20180307121128.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n```python \noversampler = SMOTE(random_state = 0) # SMOTE随机生成数据 生成只能是训练集生成数据, 而测试集不生成\n# 只生成训练集数据 使得Class为1和为0的数量相同 返回训练集的特征和分类\nos_features, os_labels = oversampler.fit_sample(features_train, labels_train)\n\nprint (\"可见 的确生成了新的数据,补充了异常的数据 \", len(os_labels[os_labels[:] == 1]), len(os_labels[os_labels[:] == 0]))\n\nprint ((os_features).shape, len(os_features[os_features == 1]), len(os_features[os_features == 0]), \n (os_labels).shape, len(os_labels[os_labels == 1]), len(os_labels[os_labels == 0]))\n```\n{% qnimg 20180307121154.png title:如图 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n```python\nos_features = pd.DataFrame(os_features)\nos_labels = pd.DataFrame(os_labels)\n# 获取最佳参数\nbest_c = printing_Kfold_scores(os_features, os_labels)\n\n# plot_confusion_matrix\n```\n\n```python\nlr = LogisticRegression(C = best_c, penalty='l1')\n# 训练 使用生成的数据\nlr.fit(os_features, os_labels.values.ravel())\n# 使用真实数据测试\ny_pred = lr.predict(features_test.values)\n\n# 打印和绘制混淆矩阵\ncnf_matrix = confusion_matrix(labels_test, y_pred)\nnp.set_printoptions(precision=2)\n\nprint (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))\n\nclass_names = [0, 1]\nplt.figure()\nplot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')\n\nplt.show()\n```\n> 个人博客 欢迎来访: http://zj2626.github.io","slug":"2018030701","published":1,"date":"2020-01-15T05:50:39.653Z","updated":"2021-03-10T13:50:15.280Z","layout":"post","photos":[],"link":"","_id":"ckm3invgr009724ujyv7th8cx","content":"

逻辑回归

\n

逻辑回归是应用非常广泛的一个分类机器学习算法,它将数据拟合到一个logit函数(或者叫做logistic函数)中,从而能够完成对事件发生的概率进行预测。

\n
\n

构建逻辑回归模型步骤:

    \n
  • 导入数据
  • \n
  • 预处理数据
  • \n
  • 对不平衡的数据进行下采样(或者过采样)处理
  • \n
  • 把处理之后的数据进行切分,切分为训训练集和测试集
  • \n
  • 对训练集进行交叉验证,同时寻找最佳的正则化参数以减少过拟合
  • \n
  • 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率
  • \n
  • 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率
  • \n
  • 修改阈值以获得更好的召回率和精确率
  • \n
\n

1. 数据与任务

信用卡欺诈数据

1
2
3
4
5
6
7
8
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline

data = pd.read_csv(\"creditcard.csv\")
data.head()
\n\"图片说明\"\n

要使用逻辑回归对数据进行建模 任务:二分类, 把数据分为有欺诈和无欺诈的两种数据

2. 使用sklearn进行数据预处理

\n

公式为:(X-mean)/std 计算时对每个属性/每列分别进行。

\n
\n

Standardization标准化:将特征数据的分布调整成标准正太分布,也叫高斯分布,也就是使得数据的均值维0,方差为1

\n

标准化的原因在于如果有些特征的方差过大,则会主导目标函数从而使参数估计器无法正确地去学习其他特征。

\n

标准化的过程为两步:去均值的中心化(均值变为0);方差的规模化(方差变为1)。

\n

在sklearn.preprocessing中提供了一个scale的方法,可以实现以上功能。如下面所示:

\n
1
2
3
4
5
6
x = np.array([[1., -1., 2.],
[2., 0., 0.],
[0., 1., -1.]])
# 将每一列特征标准化为标准正太分布,注意,标准化是针对每一列而言的
x_scale = preprocessing.scale(x)
x_scale
\n

preprocessing这个模块还提供了一个实用类StandarScaler,它可以在训练数据集上做了标准转换操作之后,把相同的转换应用到测试训练集中。
可以对训练数据,测试数据应用相同的转换,以后有新的数据进来也可以直接调用,不用再重新把数据放在一起再计算一次了。

\n
1
2
3
4
5
6
7
8
9
# 调用fit方法,根据已有的训练数据创建一个标准化的转换器
scaler = preprocessing.StandardScaler().fit(x)

scaler

StandardScaler(copy=True, with_mean=True, with_std=True)

# 使用上面这个转换器去转换训练数据x,调用transform方法
scaler.transform(x)
\n

StandardScaler()中可以传入两个参数:with_mean,with_std.这两个都是布尔型的参数,默认情况下都是true,但也可以自定义成false.即不要均值中心化或者不要方差规模化为1.

\n

1. 处理数据 数据下采样

1.1 预处理数据,修改列”Amount”数据分布

1
2
3
4
5
6
7
# 导入预处理sklearn中预处理模块的标准化模块
from sklearn.preprocessing import StandardScaler

if 'Amount' in data.columns:
# 转化特征为新的特征
data['normAount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1)) # reshape:改变数组的形状,参数为改变后的行列数
# fit_transform:对数据进行变换 矩阵旋转:-1表示自动识别 根据另一个矩阵列(行)数确定本行(列)数
\n

1.2 数据处理,去除不需要的特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 去掉两个没用的特征(列) axis=1表示对每一行去做这个操作,axis=0表示对每一列做相同的这个操作
if ('Time') in data.columns:
data = data.drop(['Time'], axis=1)
if ('Amount') in data.columns:
data = data.drop(['Amount'], axis=1)
print(data.columns, len(data.columns))

# 1.2.1 数据图形化展示(1的数据太少索引看上去没有)
count_classes = pd.value_counts(data['Class'], sort=True).sort_index() # 画图显示按某列分类之后的数据数量比例
count_classes.plot(kind = 'bar') # bar:条形图
plt.xlabel(\"Class\")
plt.ylabel(\"Frequency\")

# 1.2.2 原数据特征和分类
X = data.loc[:, data.columns != \"Class\"]
y = data.loc[:, data.columns == 'Class']
print (\"SHAPE\", X.shape, y.shape)

# Class为0的数量远远大于1的数据,需要使数据个数相近 解决方案: 1.下采样(多的数据抽取部分) 2.过采样(少的数据生成更多)
\n\"图片说明\"\n

3.下采样

\n

把数据相对多的减少,可减少为和数据少的数量相同的数量

\n
\n

1.3 区分正常数据和异常数据: 通过特征’Class’区分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.3.1 异常数据-信息
number_records = data[data.Class == 1]
# 1.3.2 异常数据个数
number_records_fraud = len(number_records)
# 1.3.3 异常数据索引
frand_indices = np.array(number_records.index)
print (\"异常样本索引 有{}个\".format(number_records_fraud), frand_indices[:10])

# 1.3.4 正常数据-索引
normal_indices = data[data.Class == 0].index
print (\"正常样本索引 有{}个\".format(len(normal_indices)), normal_indices[-10:])

print (\">>>>>>>>>>>>>>>>>>>>所有数据 正常异常比 \", len(normal_indices), '\\t', number_records_fraud)
print(\"**************\")
\n

1.4 下采样处理数据 把多的一方数据进行随机减少到与少的一方相同

1
2
3
4
5
# 在所有的正常样本索引normal_indices中随机获取,随机选取number_records_fraud个
# np.random.choice: 可以从一个int数字或1维array里随机选取内容,并将选取结果放入n维array中返回。
random_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace=False)
random_normal_indices = np.array(random_normal_indices)
print (\"'下采样'后有正常样本个数:\", len(random_normal_indices))
\n

1.5 数据索引合并 (意思就是把新的正常数据和原来的异常数据进行拼接)

1
2
3
4
5
6
under_sample_indices = np.concatenate([frand_indices, random_normal_indices])
print (\"合并后有样本个数:\", len(under_sample_indices))
under_sample_data = data.iloc[under_sample_indices, :]
print (\"合并后样本:\", under_sample_data[:1])

print (\">>>>>>>>>>>>>>>>>>>>下采样数据 正常异常比 \", len(under_sample_data[under_sample_data == 0]), '\\t', len(under_sample_data[under_sample_data == 1]))
\n

1.6 获取合并数据中的feature(特征)和label(分类)

1
2
3
4
5
X_undersample = under_sample_data.loc[:, under_sample_data.columns != 'Class']
y_undersample = under_sample_data.loc[:, under_sample_data.columns == 'Class']

print (X_undersample.shape, y_undersample.shape)
print (len(under_sample_data[under_sample_data[\"Class\"] == 1]), len(under_sample_data[under_sample_data[\"Class\"] == 0]))
\n\"图片说明\"\n

2. 切分数据为训练和测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 切分原始数据 取数据集中80%的数据作为训练集(建立model) 其他20%的为测试集(测试model)
from sklearn.cross_validation import train_test_split

print (X.shape, y.shape)
## 2.1.对原始数据进行切分 (最终需要使用原数据集中的测试数据进行测试)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) # test_size测试集所占比例 random_state切分之前进行乱序
print (\"1.训练集数据大小\", X_train.shape)
print (\"2.测试集数据大小\", X_test.shape)
print (len(X_train) + len(X_test), len(y_train), len(y_test), \"\\n\")

## 2.2.对下采样数据进行切分
X_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split(X_undersample, y_undersample, test_size=0.2, random_state=0) # test_size测试集大小 random_state切分之前进行乱序
print (\"3.训练集数据大小\", X_train_undersample.shape)
print (\"4.测试集数据大小\", X_test_undersample.shape)
print (len(X_train_undersample) + len(X_test_undersample), len(y_train_undersample), len(y_test_undersample), \"\\n\")

# 切分训练集 把训练集平均切分为三分然后进行交叉验证 (三组数据分别进行建模和验证)
\n\"图片说明\"\n

评估标准:召回率(recall)

不适用准确率,因为准确率不能正确的得到所求的,是没用的

\n

模型评估表:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
相关(Relevant),正类不相关(NonRelevant),负类
被检测到(Retrieved)true positives (TP)false positives (FP)
未被检测到(Retrieved)false negatives (FN)true negatives (TN)
\n

一些术语:

    \n
  • TP:True Positive,即正确预测出的正样本个数
  • \n
  • FP:False Positive,即错误预测出的正样本个数(本来是负样本,被我们预测成了正样本)
  • \n
  • TN:True Negative,即正确预测出的负样本个数
  • \n
  • FN:False Negative,即错误预测出的负样本个数(本来是正样本,被我们预测成了负样本)
  • \n
\n

分类器性能评价指标

由以上四个指标,可以进一步衍生出其他三个常用的评价分类器性能的指标

\n
    \n
  • Precision(精确率):TP÷(TP+FP)TP÷(TP+FP),分类器预测出的正样本中,真实正样本的比例
  • \n
  • Recall(召回率):TP÷(TP+FN)TP÷(TP+FN),在所有真实正样本中,分类器中能找到多少
  • \n
  • Accuracy(准确率):(TP+TN)÷(TP+NP+TN+FN)(TP+TN)÷(TP+NP+TN+FN),分类器对整体的判断能力,即正确预测的比例
  • \n
\n
\n

过拟合: 数据在训练集表现很好 在测试集表现很差

\n
\n
1
2
3
4
from sklearn.linear_model import LogisticRegression  # 逻辑回归
# 注意这里导入的 不是from sklearn.model_selection import KFold
from sklearn.cross_validation import KFold # 交叉验证 # cross_val_score
from sklearn.metrics import confusion_matrix, recall_score, classification_report # 混淆矩阵
\n

3. 通过多次循环交叉验证 确定正则化参数 random_state:随机种子数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def printing_Kfold_scores(x_train_data, y_train_data):
# KFold:切分数据集 (这里切分为5部分) shuffle:是否每次都\"洗牌\"(Falses时,其效果等同于random_state等于整数,每次划分的结果相同)
fold = KFold(len(y_train_data), 5, shuffle=False)
print (type(fold), len(y_train_data), len(fold)) # 长度是5

# 正则化惩罚项(正则化参数) 预设了多个惩罚值,具体使用哪个需要尝试 列举了5个
c_param_range = [0.01, 0.1, 1, 10, 100]

# 新建DataFrame类型的数据用来存放不同正则化之后的结果
results_table = pd.DataFrame(index = range(len(c_param_range)), columns = ['C_parameter', 'Mean recall score'])
results_table['C_parameter'] = c_param_range

# 先按照正则化参数进行循环以确定最好的参数 然后对每个逻辑回归进行交叉验证以获得最好的逻辑回归函数
# 循环正则化参数 获取最好的c参数
for index, c_param in enumerate(c_param_range):
print (\">>>>>>>>>>>>>>>>>>>>>>>>>>\")
print (\"C_parameter \", c_param)

recall_accs = []
# 循环进行交叉验证
# 每次循环次数为数据切分的大小,切分为n块就交叉验证n次,每次都是区其中n-1块为训练集1块为验证集
# start=1:开始索引为1
# iteration为索引 indices为划分好的数据:其中有n-1数据大小的训练集以及1数据代销的验证集
# 循环中集合每次都不一样,所有的数据都会当一次验证集:例如 三个数据[1,2,3],循环使得数据分别为训练和验证每次为:[[1],[2, 3]], [[2],[1, 3]], [[3],[1, 2]]
for iteration, indices in enumerate(fold, start=1):
# 这里并不是用fold直接划分训练集数据, 而是把索引进行1:5的划分, 然后按照索引获取数据中的对应的数据
print (iteration, len(indices[0]), len(indices[1]))

# 建立逻辑回归模型
lr = LogisticRegression(C = c_param, penalty = 'l1') # C:正则化参数; penalty:惩罚项:使用L1正则化(惩罚) ‘l1’ or ‘l2’(默认: ‘l2’)
# 在调参时如果我们主要的目的只是为了解决过拟合,一般penalty选择L2正则化就够了。
# 但是如果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。
# 另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化。
# print (\"LR-逻辑回归表达式---\", lr)

# 训练 参数一:训练数据特征(feature) 参数二:训练数据分类(label)
lr.fit(x_train_data.iloc[indices[0],:], y_train_data.iloc[indices[0],:].values.ravel())

# 预测
y_pred_undersample = lr.predict(x_train_data.iloc[indices[1], :].values)

# 计算召回率 召回率 =提取出的正确信息条数 /样本中的信息条数。通俗地说,就是所有准确的条目有多少被检索出来了。
# 参数: 1.真实数据集 2.预测数据集
recall_acc = recall_score(y_train_data.iloc[indices[1],:].values, y_pred_undersample)
recall_accs.append(recall_acc)
print (len(indices), \"Iteration \", iteration, \": recall score = \", recall_acc)

# 求每个惩罚值经过交叉验证之后平均召回率
results_table.loc[index, 'Mean recall score'] = np.mean(recall_accs)

print ('\\nMean recall score ', np.mean(recall_accs), '\\n')

print (results_table)

best_c = results_table.loc[results_table['Mean recall score'].idxmax()]['C_parameter']
print (\"finally-------best is--------> \", best_c)

return best_c

best_c = printing_Kfold_scores(X_train_undersample, y_train_undersample)
\n\"图片说明\"\n\"图片说明\"\n\"图片说明\"\n

ndarray数据格式化: set_printoptions

\n

set_printoptions(precision=None,
threshold=None,
edgeitems=None,
linewidth=None,
suppress=None,
nanstr=None,
infstr=None,
formatter=None)

\n
\n
    \n
  • precision:输出结果保留精度的位数 (num)
  • \n
  • threshold:array数量的个数在小于threshold的时候不会被折叠 (num)
  • \n
  • edgeitems:在array已经被折叠后,开头和结尾都会显示edgeitems个数 (num)
  • \n
  • formatter:这个很有意思,像python3里面str.format(),就是可以对你的输出进行自定义的格式化 其他的暂时没用到
  • \n
\n

4. 使用最好的正则化参数 构建逻辑回归模型并进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 构建逻辑回归模型
lr = LogisticRegression(C = best_c, penalty='l1')
# 训练回归模型
lr.fit(X_train_undersample, y_train_undersample.values.ravel())
# 使用模型进行测试
y_pred_undersample = lr.predict(X_test_undersample.values)
# y_pred_undersample为预测(分类)值, y_test_undersample为真实测试集的(分类)值

print (type(y_pred_undersample), len(y_pred_undersample), \"\\n\")

# 打印和绘制混淆矩阵
import itertools
def plot_confusion_matrix(cm, classes, title='Confussion matrix', cmap=plt.cm.Blues):
#设置显示混淆矩阵
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()

# 设置坐标数
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=0)
plt.yticks(tick_marks, classes)

thresh = cm.max() / 2

# itertools.product可进行多层次循环 传入参数个数(n)和索引个数相同 可循环n^2次
# 设置每个方块中的文字
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
# print (j, i, cm[i, j])
# 因为i表示横坐标的位置, j表示纵坐标的位置 所以需要把i和j交换位置
plt.text(j, i, cm[i, j], horizontalalignment=\"center\", color=\"white\" if cm[i, j] > thresh else \"black\")

plt.tight_layout()
# 设置坐标文字
plt.ylabel(\"True label\") # 真实数据
plt.xlabel(\"Predicted label\") # 预测数据 1表示正例 0表示负例

# 画混淆矩阵图 参数: 1.y_true, 2.y_pred
cnf_matrix = confusion_matrix(y_test_undersample, y_pred_undersample)
np.set_printoptions(precision=2)

print (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')
plt.show()

# 由图可见, 召回率为 85 / (85 + 6) = 93.41%
# 精确率为 (85) / (85 + 9) = 90.43%
# 准确率为 (85 + 97) / (85 + 9 + 6 + 97) = 92.39%

# 以上计算都是基于下采样数据集的,还需要在原数据的测试集上进行测试操作 (与上面同理)
\n\"图片说明\"\n

4. 使用最好的正则化参数 构建逻辑回归模型并进行测试 (使用原始数据的测试集和训练集)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 在原数据的测试集上进行测试操作
lr = LogisticRegression(C = best_c, penalty='l1')
lr.fit(X_train, y_train.values.ravel())
y_pred = lr.predict(X_test.values)
# y_pred为预测(分类)值, y_test为真实测试集的(分类)值


cnf_matrix = confusion_matrix(y_test, y_pred)
np.set_printoptions(precision=2)

print (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')
plt.show()
\n

5. 修改阈值以获取最好的逻辑回归模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 阈值: 默认使用sigma函数默认值:0.5, 意思是当预测概率大于0.5表示True,概率小鱼0.5表示False

lr = LogisticRegression(C = best_c, penalty='l1')
# 训练
lr.fit(X_train, y_train.values.ravel())
# 预测 这里是预测概率值 每个数据的预测包含两个值,对于二分类问题,也就是被判断为0的概率和被判断为1的概率
y_pred_undersample_proba = lr.predict_proba(X_test_undersample.values) # 预测概率值而不是类别值

# 可能的阈值
thresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

plt.figure(figsize=(10, 10)) # 画图域

for index, i in enumerate(thresholds):
# 预测概率
y_test_predictions_high_recall = y_pred_undersample_proba[:, 1] > i

plt.subplot(3, 3, index + 1)

cnf_matrix = confusion_matrix(y_test_undersample, y_test_predictions_high_recall)
np.set_printoptions(precision=2)

print (i, \"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plot_confusion_matrix(cnf_matrix, classes=class_names, title=\"Threshold >= %s\" %i)

## 随着阈值上升 召回率不断变化 其中本来是1的被误检测为0的越来越多 可见 要选取最合适的阈值以达到召回率最高
\n\"图片说明\"\n\"图片说明\"\n\"图片说明\"\n

过采样

\n

把数据相对少的增加,可增加为和数据多的数量相同的数量 (生成)

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split

credit_cards = pd.read_csv(\"creditcard.csv\")
columns = credit_cards.columns

features_columns = columns.delete(len(columns) - 1) #删除最后一列数据
print (features_columns)

features = credit_cards[features_columns]
labels = credit_cards['Class']

print (\"原始的数据个数\", (credit_cards[credit_cards['Class'] == 0]).shape, (credit_cards[credit_cards['Class'] == 1]).shape)
\n
1
2
features_train, features_test, labels_train, labels_test = train_test_split(features, labels, test_size=0.2, random_state=0)
print (features_train.shape, features_test.shape, labels_train.shape, labels_test.shape)
\n\"图片说明\"\n
1
2
3
4
5
6
7
8
oversampler = SMOTE(random_state = 0) # SMOTE随机生成数据 生成只能是训练集生成数据, 而测试集不生成
# 只生成训练集数据 使得Class为1和为0的数量相同 返回训练集的特征和分类
os_features, os_labels = oversampler.fit_sample(features_train, labels_train)

print (\"可见 的确生成了新的数据,补充了异常的数据 \", len(os_labels[os_labels[:] == 1]), len(os_labels[os_labels[:] == 0]))

print ((os_features).shape, len(os_features[os_features == 1]), len(os_features[os_features == 0]),
(os_labels).shape, len(os_labels[os_labels == 1]), len(os_labels[os_labels == 0]))
\n\"图片说明\"\n
1
2
3
4
5
6
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
# 获取最佳参数
best_c = printing_Kfold_scores(os_features, os_labels)

# plot_confusion_matrix
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
lr = LogisticRegression(C = best_c, penalty='l1')
# 训练 使用生成的数据
lr.fit(os_features, os_labels.values.ravel())
# 使用真实数据测试
y_pred = lr.predict(features_test.values)

# 打印和绘制混淆矩阵
cnf_matrix = confusion_matrix(labels_test, y_pred)
np.set_printoptions(precision=2)

print (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')

plt.show()
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n","site":{"data":{}},"excerpt":"","more":"

逻辑回归

\n

逻辑回归是应用非常广泛的一个分类机器学习算法,它将数据拟合到一个logit函数(或者叫做logistic函数)中,从而能够完成对事件发生的概率进行预测。

\n
\n

构建逻辑回归模型步骤:

    \n
  • 导入数据
  • \n
  • 预处理数据
  • \n
  • 对不平衡的数据进行下采样(或者过采样)处理
  • \n
  • 把处理之后的数据进行切分,切分为训训练集和测试集
  • \n
  • 对训练集进行交叉验证,同时寻找最佳的正则化参数以减少过拟合
  • \n
  • 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率
  • \n
  • 使用最佳的正则化参数对处理之后的数据进行训练并预测,观察召回率和精确率
  • \n
  • 修改阈值以获得更好的召回率和精确率
  • \n
\n

1. 数据与任务

信用卡欺诈数据

1
2
3
4
5
6
7
8
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline

data = pd.read_csv(\"creditcard.csv\")
data.head()
\n\"图片说明\"\n

要使用逻辑回归对数据进行建模 任务:二分类, 把数据分为有欺诈和无欺诈的两种数据

2. 使用sklearn进行数据预处理

\n

公式为:(X-mean)/std 计算时对每个属性/每列分别进行。

\n
\n

Standardization标准化:将特征数据的分布调整成标准正太分布,也叫高斯分布,也就是使得数据的均值维0,方差为1

\n

标准化的原因在于如果有些特征的方差过大,则会主导目标函数从而使参数估计器无法正确地去学习其他特征。

\n

标准化的过程为两步:去均值的中心化(均值变为0);方差的规模化(方差变为1)。

\n

在sklearn.preprocessing中提供了一个scale的方法,可以实现以上功能。如下面所示:

\n
1
2
3
4
5
6
x = np.array([[1., -1., 2.],
[2., 0., 0.],
[0., 1., -1.]])
# 将每一列特征标准化为标准正太分布,注意,标准化是针对每一列而言的
x_scale = preprocessing.scale(x)
x_scale
\n

preprocessing这个模块还提供了一个实用类StandarScaler,它可以在训练数据集上做了标准转换操作之后,把相同的转换应用到测试训练集中。
可以对训练数据,测试数据应用相同的转换,以后有新的数据进来也可以直接调用,不用再重新把数据放在一起再计算一次了。

\n
1
2
3
4
5
6
7
8
9
# 调用fit方法,根据已有的训练数据创建一个标准化的转换器
scaler = preprocessing.StandardScaler().fit(x)

scaler

StandardScaler(copy=True, with_mean=True, with_std=True)

# 使用上面这个转换器去转换训练数据x,调用transform方法
scaler.transform(x)
\n

StandardScaler()中可以传入两个参数:with_mean,with_std.这两个都是布尔型的参数,默认情况下都是true,但也可以自定义成false.即不要均值中心化或者不要方差规模化为1.

\n

1. 处理数据 数据下采样

1.1 预处理数据,修改列”Amount”数据分布

1
2
3
4
5
6
7
# 导入预处理sklearn中预处理模块的标准化模块
from sklearn.preprocessing import StandardScaler

if 'Amount' in data.columns:
# 转化特征为新的特征
data['normAount'] = StandardScaler().fit_transform(data['Amount'].reshape(-1, 1)) # reshape:改变数组的形状,参数为改变后的行列数
# fit_transform:对数据进行变换 矩阵旋转:-1表示自动识别 根据另一个矩阵列(行)数确定本行(列)数
\n

1.2 数据处理,去除不需要的特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 去掉两个没用的特征(列) axis=1表示对每一行去做这个操作,axis=0表示对每一列做相同的这个操作
if ('Time') in data.columns:
data = data.drop(['Time'], axis=1)
if ('Amount') in data.columns:
data = data.drop(['Amount'], axis=1)
print(data.columns, len(data.columns))

# 1.2.1 数据图形化展示(1的数据太少索引看上去没有)
count_classes = pd.value_counts(data['Class'], sort=True).sort_index() # 画图显示按某列分类之后的数据数量比例
count_classes.plot(kind = 'bar') # bar:条形图
plt.xlabel(\"Class\")
plt.ylabel(\"Frequency\")

# 1.2.2 原数据特征和分类
X = data.loc[:, data.columns != \"Class\"]
y = data.loc[:, data.columns == 'Class']
print (\"SHAPE\", X.shape, y.shape)

# Class为0的数量远远大于1的数据,需要使数据个数相近 解决方案: 1.下采样(多的数据抽取部分) 2.过采样(少的数据生成更多)
\n\"图片说明\"\n

3.下采样

\n

把数据相对多的减少,可减少为和数据少的数量相同的数量

\n
\n

1.3 区分正常数据和异常数据: 通过特征’Class’区分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.3.1 异常数据-信息
number_records = data[data.Class == 1]
# 1.3.2 异常数据个数
number_records_fraud = len(number_records)
# 1.3.3 异常数据索引
frand_indices = np.array(number_records.index)
print (\"异常样本索引 有{}个\".format(number_records_fraud), frand_indices[:10])

# 1.3.4 正常数据-索引
normal_indices = data[data.Class == 0].index
print (\"正常样本索引 有{}个\".format(len(normal_indices)), normal_indices[-10:])

print (\">>>>>>>>>>>>>>>>>>>>所有数据 正常异常比 \", len(normal_indices), '\\t', number_records_fraud)
print(\"**************\")
\n

1.4 下采样处理数据 把多的一方数据进行随机减少到与少的一方相同

1
2
3
4
5
# 在所有的正常样本索引normal_indices中随机获取,随机选取number_records_fraud个
# np.random.choice: 可以从一个int数字或1维array里随机选取内容,并将选取结果放入n维array中返回。
random_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace=False)
random_normal_indices = np.array(random_normal_indices)
print (\"'下采样'后有正常样本个数:\", len(random_normal_indices))
\n

1.5 数据索引合并 (意思就是把新的正常数据和原来的异常数据进行拼接)

1
2
3
4
5
6
under_sample_indices = np.concatenate([frand_indices, random_normal_indices])
print (\"合并后有样本个数:\", len(under_sample_indices))
under_sample_data = data.iloc[under_sample_indices, :]
print (\"合并后样本:\", under_sample_data[:1])

print (\">>>>>>>>>>>>>>>>>>>>下采样数据 正常异常比 \", len(under_sample_data[under_sample_data == 0]), '\\t', len(under_sample_data[under_sample_data == 1]))
\n

1.6 获取合并数据中的feature(特征)和label(分类)

1
2
3
4
5
X_undersample = under_sample_data.loc[:, under_sample_data.columns != 'Class']
y_undersample = under_sample_data.loc[:, under_sample_data.columns == 'Class']

print (X_undersample.shape, y_undersample.shape)
print (len(under_sample_data[under_sample_data[\"Class\"] == 1]), len(under_sample_data[under_sample_data[\"Class\"] == 0]))
\n\"图片说明\"\n

2. 切分数据为训练和测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 切分原始数据 取数据集中80%的数据作为训练集(建立model) 其他20%的为测试集(测试model)
from sklearn.cross_validation import train_test_split

print (X.shape, y.shape)
## 2.1.对原始数据进行切分 (最终需要使用原数据集中的测试数据进行测试)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) # test_size测试集所占比例 random_state切分之前进行乱序
print (\"1.训练集数据大小\", X_train.shape)
print (\"2.测试集数据大小\", X_test.shape)
print (len(X_train) + len(X_test), len(y_train), len(y_test), \"\\n\")

## 2.2.对下采样数据进行切分
X_train_undersample, X_test_undersample, y_train_undersample, y_test_undersample = train_test_split(X_undersample, y_undersample, test_size=0.2, random_state=0) # test_size测试集大小 random_state切分之前进行乱序
print (\"3.训练集数据大小\", X_train_undersample.shape)
print (\"4.测试集数据大小\", X_test_undersample.shape)
print (len(X_train_undersample) + len(X_test_undersample), len(y_train_undersample), len(y_test_undersample), \"\\n\")

# 切分训练集 把训练集平均切分为三分然后进行交叉验证 (三组数据分别进行建模和验证)
\n\"图片说明\"\n

评估标准:召回率(recall)

不适用准确率,因为准确率不能正确的得到所求的,是没用的

\n

模型评估表:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
相关(Relevant),正类不相关(NonRelevant),负类
被检测到(Retrieved)true positives (TP)false positives (FP)
未被检测到(Retrieved)false negatives (FN)true negatives (TN)
\n

一些术语:

    \n
  • TP:True Positive,即正确预测出的正样本个数
  • \n
  • FP:False Positive,即错误预测出的正样本个数(本来是负样本,被我们预测成了正样本)
  • \n
  • TN:True Negative,即正确预测出的负样本个数
  • \n
  • FN:False Negative,即错误预测出的负样本个数(本来是正样本,被我们预测成了负样本)
  • \n
\n

分类器性能评价指标

由以上四个指标,可以进一步衍生出其他三个常用的评价分类器性能的指标

\n
    \n
  • Precision(精确率):TP÷(TP+FP)TP÷(TP+FP),分类器预测出的正样本中,真实正样本的比例
  • \n
  • Recall(召回率):TP÷(TP+FN)TP÷(TP+FN),在所有真实正样本中,分类器中能找到多少
  • \n
  • Accuracy(准确率):(TP+TN)÷(TP+NP+TN+FN)(TP+TN)÷(TP+NP+TN+FN),分类器对整体的判断能力,即正确预测的比例
  • \n
\n
\n

过拟合: 数据在训练集表现很好 在测试集表现很差

\n
\n
1
2
3
4
from sklearn.linear_model import LogisticRegression  # 逻辑回归
# 注意这里导入的 不是from sklearn.model_selection import KFold
from sklearn.cross_validation import KFold # 交叉验证 # cross_val_score
from sklearn.metrics import confusion_matrix, recall_score, classification_report # 混淆矩阵
\n

3. 通过多次循环交叉验证 确定正则化参数 random_state:随机种子数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def printing_Kfold_scores(x_train_data, y_train_data):
# KFold:切分数据集 (这里切分为5部分) shuffle:是否每次都\"洗牌\"(Falses时,其效果等同于random_state等于整数,每次划分的结果相同)
fold = KFold(len(y_train_data), 5, shuffle=False)
print (type(fold), len(y_train_data), len(fold)) # 长度是5

# 正则化惩罚项(正则化参数) 预设了多个惩罚值,具体使用哪个需要尝试 列举了5个
c_param_range = [0.01, 0.1, 1, 10, 100]

# 新建DataFrame类型的数据用来存放不同正则化之后的结果
results_table = pd.DataFrame(index = range(len(c_param_range)), columns = ['C_parameter', 'Mean recall score'])
results_table['C_parameter'] = c_param_range

# 先按照正则化参数进行循环以确定最好的参数 然后对每个逻辑回归进行交叉验证以获得最好的逻辑回归函数
# 循环正则化参数 获取最好的c参数
for index, c_param in enumerate(c_param_range):
print (\">>>>>>>>>>>>>>>>>>>>>>>>>>\")
print (\"C_parameter \", c_param)

recall_accs = []
# 循环进行交叉验证
# 每次循环次数为数据切分的大小,切分为n块就交叉验证n次,每次都是区其中n-1块为训练集1块为验证集
# start=1:开始索引为1
# iteration为索引 indices为划分好的数据:其中有n-1数据大小的训练集以及1数据代销的验证集
# 循环中集合每次都不一样,所有的数据都会当一次验证集:例如 三个数据[1,2,3],循环使得数据分别为训练和验证每次为:[[1],[2, 3]], [[2],[1, 3]], [[3],[1, 2]]
for iteration, indices in enumerate(fold, start=1):
# 这里并不是用fold直接划分训练集数据, 而是把索引进行1:5的划分, 然后按照索引获取数据中的对应的数据
print (iteration, len(indices[0]), len(indices[1]))

# 建立逻辑回归模型
lr = LogisticRegression(C = c_param, penalty = 'l1') # C:正则化参数; penalty:惩罚项:使用L1正则化(惩罚) ‘l1’ or ‘l2’(默认: ‘l2’)
# 在调参时如果我们主要的目的只是为了解决过拟合,一般penalty选择L2正则化就够了。
# 但是如果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。
# 另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,从而让模型系数稀疏化的话,也可以使用L1正则化。
# print (\"LR-逻辑回归表达式---\", lr)

# 训练 参数一:训练数据特征(feature) 参数二:训练数据分类(label)
lr.fit(x_train_data.iloc[indices[0],:], y_train_data.iloc[indices[0],:].values.ravel())

# 预测
y_pred_undersample = lr.predict(x_train_data.iloc[indices[1], :].values)

# 计算召回率 召回率 =提取出的正确信息条数 /样本中的信息条数。通俗地说,就是所有准确的条目有多少被检索出来了。
# 参数: 1.真实数据集 2.预测数据集
recall_acc = recall_score(y_train_data.iloc[indices[1],:].values, y_pred_undersample)
recall_accs.append(recall_acc)
print (len(indices), \"Iteration \", iteration, \": recall score = \", recall_acc)

# 求每个惩罚值经过交叉验证之后平均召回率
results_table.loc[index, 'Mean recall score'] = np.mean(recall_accs)

print ('\\nMean recall score ', np.mean(recall_accs), '\\n')

print (results_table)

best_c = results_table.loc[results_table['Mean recall score'].idxmax()]['C_parameter']
print (\"finally-------best is--------> \", best_c)

return best_c

best_c = printing_Kfold_scores(X_train_undersample, y_train_undersample)
\n\"图片说明\"\n\"图片说明\"\n\"图片说明\"\n

ndarray数据格式化: set_printoptions

\n

set_printoptions(precision=None,
threshold=None,
edgeitems=None,
linewidth=None,
suppress=None,
nanstr=None,
infstr=None,
formatter=None)

\n
\n
    \n
  • precision:输出结果保留精度的位数 (num)
  • \n
  • threshold:array数量的个数在小于threshold的时候不会被折叠 (num)
  • \n
  • edgeitems:在array已经被折叠后,开头和结尾都会显示edgeitems个数 (num)
  • \n
  • formatter:这个很有意思,像python3里面str.format(),就是可以对你的输出进行自定义的格式化 其他的暂时没用到
  • \n
\n

4. 使用最好的正则化参数 构建逻辑回归模型并进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 构建逻辑回归模型
lr = LogisticRegression(C = best_c, penalty='l1')
# 训练回归模型
lr.fit(X_train_undersample, y_train_undersample.values.ravel())
# 使用模型进行测试
y_pred_undersample = lr.predict(X_test_undersample.values)
# y_pred_undersample为预测(分类)值, y_test_undersample为真实测试集的(分类)值

print (type(y_pred_undersample), len(y_pred_undersample), \"\\n\")

# 打印和绘制混淆矩阵
import itertools
def plot_confusion_matrix(cm, classes, title='Confussion matrix', cmap=plt.cm.Blues):
#设置显示混淆矩阵
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()

# 设置坐标数
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=0)
plt.yticks(tick_marks, classes)

thresh = cm.max() / 2

# itertools.product可进行多层次循环 传入参数个数(n)和索引个数相同 可循环n^2次
# 设置每个方块中的文字
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
# print (j, i, cm[i, j])
# 因为i表示横坐标的位置, j表示纵坐标的位置 所以需要把i和j交换位置
plt.text(j, i, cm[i, j], horizontalalignment=\"center\", color=\"white\" if cm[i, j] > thresh else \"black\")

plt.tight_layout()
# 设置坐标文字
plt.ylabel(\"True label\") # 真实数据
plt.xlabel(\"Predicted label\") # 预测数据 1表示正例 0表示负例

# 画混淆矩阵图 参数: 1.y_true, 2.y_pred
cnf_matrix = confusion_matrix(y_test_undersample, y_pred_undersample)
np.set_printoptions(precision=2)

print (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')
plt.show()

# 由图可见, 召回率为 85 / (85 + 6) = 93.41%
# 精确率为 (85) / (85 + 9) = 90.43%
# 准确率为 (85 + 97) / (85 + 9 + 6 + 97) = 92.39%

# 以上计算都是基于下采样数据集的,还需要在原数据的测试集上进行测试操作 (与上面同理)
\n\"图片说明\"\n

4. 使用最好的正则化参数 构建逻辑回归模型并进行测试 (使用原始数据的测试集和训练集)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 在原数据的测试集上进行测试操作
lr = LogisticRegression(C = best_c, penalty='l1')
lr.fit(X_train, y_train.values.ravel())
y_pred = lr.predict(X_test.values)
# y_pred为预测(分类)值, y_test为真实测试集的(分类)值


cnf_matrix = confusion_matrix(y_test, y_pred)
np.set_printoptions(precision=2)

print (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1]/(cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')
plt.show()
\n

5. 修改阈值以获取最好的逻辑回归模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 阈值: 默认使用sigma函数默认值:0.5, 意思是当预测概率大于0.5表示True,概率小鱼0.5表示False

lr = LogisticRegression(C = best_c, penalty='l1')
# 训练
lr.fit(X_train, y_train.values.ravel())
# 预测 这里是预测概率值 每个数据的预测包含两个值,对于二分类问题,也就是被判断为0的概率和被判断为1的概率
y_pred_undersample_proba = lr.predict_proba(X_test_undersample.values) # 预测概率值而不是类别值

# 可能的阈值
thresholds = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

plt.figure(figsize=(10, 10)) # 画图域

for index, i in enumerate(thresholds):
# 预测概率
y_test_predictions_high_recall = y_pred_undersample_proba[:, 1] > i

plt.subplot(3, 3, index + 1)

cnf_matrix = confusion_matrix(y_test_undersample, y_test_predictions_high_recall)
np.set_printoptions(precision=2)

print (i, \"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plot_confusion_matrix(cnf_matrix, classes=class_names, title=\"Threshold >= %s\" %i)

## 随着阈值上升 召回率不断变化 其中本来是1的被误检测为0的越来越多 可见 要选取最合适的阈值以达到召回率最高
\n\"图片说明\"\n\"图片说明\"\n\"图片说明\"\n

过采样

\n

把数据相对少的增加,可增加为和数据多的数量相同的数量 (生成)

\n
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
from imblearn.over_sampling import SMOTE
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split

credit_cards = pd.read_csv(\"creditcard.csv\")
columns = credit_cards.columns

features_columns = columns.delete(len(columns) - 1) #删除最后一列数据
print (features_columns)

features = credit_cards[features_columns]
labels = credit_cards['Class']

print (\"原始的数据个数\", (credit_cards[credit_cards['Class'] == 0]).shape, (credit_cards[credit_cards['Class'] == 1]).shape)
\n
1
2
features_train, features_test, labels_train, labels_test = train_test_split(features, labels, test_size=0.2, random_state=0)
print (features_train.shape, features_test.shape, labels_train.shape, labels_test.shape)
\n\"图片说明\"\n
1
2
3
4
5
6
7
8
oversampler = SMOTE(random_state = 0) # SMOTE随机生成数据 生成只能是训练集生成数据, 而测试集不生成
# 只生成训练集数据 使得Class为1和为0的数量相同 返回训练集的特征和分类
os_features, os_labels = oversampler.fit_sample(features_train, labels_train)

print (\"可见 的确生成了新的数据,补充了异常的数据 \", len(os_labels[os_labels[:] == 1]), len(os_labels[os_labels[:] == 0]))

print ((os_features).shape, len(os_features[os_features == 1]), len(os_features[os_features == 0]),
(os_labels).shape, len(os_labels[os_labels == 1]), len(os_labels[os_labels == 0]))
\n\"图片说明\"\n
1
2
3
4
5
6
os_features = pd.DataFrame(os_features)
os_labels = pd.DataFrame(os_labels)
# 获取最佳参数
best_c = printing_Kfold_scores(os_features, os_labels)

# plot_confusion_matrix
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
lr = LogisticRegression(C = best_c, penalty='l1')
# 训练 使用生成的数据
lr.fit(os_features, os_labels.values.ravel())
# 使用真实数据测试
y_pred = lr.predict(features_test.values)

# 打印和绘制混淆矩阵
cnf_matrix = confusion_matrix(labels_test, y_pred)
np.set_printoptions(precision=2)

print (\"Recall metric in the testing dataset: \", cnf_matrix[1, 1] / (cnf_matrix[1, 0] + cnf_matrix[1, 1]))

class_names = [0, 1]
plt.figure()
plot_confusion_matrix(cnf_matrix, classes=class_names, title='Confusion matrix')

plt.show()
\n
\n

个人博客 欢迎来访: http://zj2626.github.io

\n
\n"},{"title":"Shiro的学习Helloworld","comments":1,"description":null,"_content":"\n## Apache Shiro是Java的一个安全框架\n### Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等\n\n{% qnimg d59f6d02-1f45-3285-8983-4ea5f18111d5.png title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n## Shiro基本功能\n>Authentication:身份认证/登录,验证用户是不是拥有相应的身份;\n\n>Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;\n\n>Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;\n\n>Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;\n\n>Web Support:Web支持,可以非常容易的集成到Web环境;\n\n>Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;\n\n>Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;\n\n>Testing:提供测试支持;\n\n>Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;\n\n>Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。\n\n\n\n## 1.身份认证\n \n 配置文件:\n ********************************\n [users]\n zj2626=123456\n ay2626=456789\n ********************************\n \n\n package com.em;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.UsernamePasswordToken;\n import org.apache.shiro.config.IniSecurityManagerFactory;\n import org.apache.shiro.mgt.SecurityManager;\n import org.apache.shiro.subject.Subject;\n import org.apache.shiro.util.Factory;\n \n public class Hello {\n \n public static void main(String args[]) {\n //初始化SecurityManager工厂 读取配置文件中的用户名密码\n Factory factory = new IniSecurityManagerFactory(\"classpath:shiro.ini\");\n //获取SecurityManager实例\n SecurityManager manager = factory.getInstance();\n //把SecurityManager实例绑定到SecurityUtils\n SecurityUtils.setSecurityManager(manager);\n //得到当前执行的用户\n Subject subject = SecurityUtils.getSubject();\n //创建token令牌, 用户/密码\n UsernamePasswordToken token = new UsernamePasswordToken(\"zj2626\", \"123456\");\n \n try {\n //身份认证 登录\n subject.login(token);\n \n System.out.println(\"登录成功\");\n } catch (Exception e) {\n System.out.println(\"登录失败\");\n e.printStackTrace();\n }\n \n subject.logout();\n }\n \n /*\n Subject: 认证主体\n Principals: 身份: 用户名\n Credentials: 凭证: 密码\n \n Realm: 域\n 1.jdbc realm | 2.jndi realm | 3.text realm\n \n */\n }\n\n\n\n从数据库中读取用户名密码 实现登录\n\n1.配置文件: jdbc_realm.ini (代码只需把读取的文件改成此文件即可测试使用)\n \n [main]\n jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm\n dataSource=com.mchange.v2.c3p0.ComboPooledDataSource\n dataSource.driverClass=com.mysql.jdbc.Driver\n dataSource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test\n dataSource.user=root\n dataSource.password=123456\n jdbcRealm.dataSource=$dataSource\n securityManager.realms=$jdbcRealm\n\n\n***\n***\n\n## 2.权限认证\n### 核心要素:(资源,)权限,角色,用户\n\n**用户--(分配)-->角色--(拥有)-->权限--(控制)---->资源**\n\n* 用户代表访问系统的用户,即subject。\n\n> **三种授权方式**\n\n \n 1. 编程式授权\n 1.1. 基于角色的访问控制\n 1.2. 基于权限的访问控制\n 2. 注解式授权\n 3.JSP标签授权\n\n \n---\n\n####步骤1: 封装一个工具类\n\n public class ShiroUtils {\n public static Subject login(String conf, String username, String passowrd) {\n //初始化SecurityManager工厂 conf是配置文件名称\n Factory factory = new IniSecurityManagerFactory(\"classpath:\" + conf + \".ini\");\n //获取SecurityManager实例\n SecurityManager manager = factory.getInstance();\n //把SecurityManager实例绑定到SecurityUtils\n SecurityUtils.setSecurityManager(manager);\n //得到当前执行的用户\n Subject subject = SecurityUtils.getSubject();\n //创建token令牌, 用户/密码\n UsernamePasswordToken token = new UsernamePasswordToken(username, passowrd);\n \n try {\n //身份认证 登录\n subject.login(token);\n } catch (Exception e) {\n e.printStackTrace();\n }\n \n return subject;\n }\n }\n\n\n####步骤2: 测试多种访问控制\n\n /*\n 基于角色的访问控制方式1\n \n 配置文件:shiro_role.ini\n ******************************\n [users]\n zj2626=123456,role1,role2\n py2626=123456,role1\n ay2626=456789,role3\n ******************************\n */\n @Test\n public void testHas() {\n Subject sub = ShiroUtils.login(\"shiro_config/shiro_role\", \"zj2626\", \"123456\");\n \n //判断有没有权限 返回布尔 表示验证的成功与否\n boolean bool = sub.hasRole(\"role1\");\n if (bool) {\n System.out.println(\"HAS\");\n }\n \n //判断有没有权限,一次多个分别判断 返回布尔数组\n boolean[] booleans = sub.hasRoles(Arrays.asList(\"role1\", \"role2\", \"role3\"));\n int i = 0;\n while (booleans[i]) {\n i++;\n \n if (booleans.length <= i) {\n break;\n }\n }\n \n //所有的角色都有才返回true\n System.out.println(sub.hasAllRoles(Arrays.asList(\"role1\", \"role2\", \"role3\")));\n \n //判断有没有权限 没有则抛异常\n sub.checkRole(\"role1\");\n sub.checkRole(\"role3\");\n \n //判断多个权限 有一个没有就抛异常 (2种参数形式)\n sub.checkRoles(Arrays.asList(\"role1\", \"role2\", \"role3\"));\n sub.checkRoles(\"role1\", \"role2\", \"role3\");\n \n //退出登陆\n sub.logout();\n }\n \n /*\n 基于权限的访问控制方式(过程同上)\n \n 配置文件:shiro_permision.ini\n ******************************\n [users]\n zj2626=123456,role1,role2\n py2626=123456,role1\n ay2626=456789,role3\n [roles]\n role1=user:select\n role2=user:add,user:update,user:delete\n ******************************\n */\n @Test\n public void testPermition() {\n Subject sub = ShiroUtils.login(\"shiro_config/shiro_permision\", \"py2626\", \"123456\");\n \n System.out.println(\"用户是否有权限 user:select:\" + sub.isPermitted(\"user:select\")); //true\n System.out.println(\"用户是否有权限 user:update:\" + sub.isPermitted(\"user:update\")); //false\n \n boolean[] booleans = sub.isPermitted(\"user:add\", \"user:select\");\n System.out.println(booleans[0] + \"____\" + booleans[1]);\n \n System.out.println(sub.isPermittedAll(\"user:add\", \"user:select\"));\n \n //没有会抛出异常\n sub.checkPermission(\"user:select\");\n sub.checkPermissions(\"user:select\", \"user:update\");\n \n sub.logout();\n }\n\n***\n***\n\n## 3.集成web进行测试\n\n#### 1.新建一个maven的web项目\n\n{% qnimg 1.png title:项目目录文件 alt:项目目录文件 extend:?imageView2/2/w/600 %}\n\n> web.xml 配置shiro的必须的配置: 监听器,过滤器\n\n\n \n \n Archetype Created Web Application\n \n \n \n loginServlet\n com.servlet.LoginServlet\n \n \n loginServlet\n /login\n \n \n homeServlet\n com.servlet.HomeServlet\n \n \n homeServlet\n /home\n \n \n adminServlet\n com.servlet.AdminServlet\n \n \n adminServlet\n /admin\n \n \n \n \n org.apache.shiro.web.env.EnvironmentLoaderListener\n \n \n \n \n ShiroFilter\n org.apache.shiro.web.servlet.ShiroFilter\n \n config\n classpath:shiro.ini\n \n \n \n ShiroFilter\n /*\n \n \n \n\n\n\n> pom.xml :所需依赖\n\n \n \n org.apache.shiro\n shiro-web\n 1.3.2\n \n \n org.apache.shiro\n shiro-core\n 1.3.2\n \n \n org.apache.shiro\n shiro-spring\n 1.3.2\n \n \n org.apache.tomcat\n tomcat-servlet-api\n 8.5.4\n provided\n \n \n org.slf4j\n slf4j-log4j12\n 1.7.7\n compile\n \n \n commons-logging\n commons-logging\n 1.2\n \n \n commons-beanutils\n commons-beanutils\n 1.9.3\n \n \n commons-collections\n commons-collections\n 3.2.1\n \n \n jstl\n jstl\n 1.2\n \n \n\n> shiro.ini: 权限配置文件,配置什么用户有什么角色,什么角色有什么权限\n\n [main]\n authc.loginUrl=/login\n perms.unauthorizedUrl=/noAuth.jsp\n roles.unauthorizedUrl=/noAuth.jsp\n [users]\n zj2626=123456,admin\n ay2626=456789,student\n [roles]\n admin=user:*,student:select\n student:student:*\n [urls]\n /login=anon\n /home=authc\n /admin=roles[admin]\n\n##### *1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址...(loginUrl是authc的一个属性)*\n##### *2.roles.unauthorizeUrl配置角色认证不通过跳转的地址...(noAuth.jsp页面目前只有一行字)*\n##### *3.perms.unauthorizeUrl配置权限认证不通过跳转的地址*\n\n##### *4.[users]下配置用户身份信息以及用户角色*\n##### *5.[roles]下配置角色以及角色的控制权限*\n##### *6.[urls]下配置访问地址所需的权限, 其中值为\"anon过滤器\"表示地址不需要登录即可访问; \"authc过滤器\"表示地址登录才能访问*\n##### *7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围*\n##### *8.值为 perms[\"student:create\"] 表示 必须有权限为\"student:create\"的用户才能范围*\n##### *9.多个过滤器用\",\"隔开 而且相互为\"且\"的关系(必须同时满足才能访问)*\n##### *10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef.....)*\n##### *11.地址可以使用*表示匹配任意个任意字符(eg: /home*=authc 表示可过滤 /home123; /homeef.....)*\n##### *12.地址可以使用**表示匹配多路径(eg: /home/**=authc 表示可过滤 /home/abc; /home/aaa/bbb.....)*\n\n#### 2.编写sevlet代码\n\n> LoginServlet.java :身份验证地址\n\n \n package com.servlet;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.UsernamePasswordToken;\n import org.apache.shiro.subject.Subject;\n \n import javax.servlet.ServletException;\n import javax.servlet.http.HttpServlet;\n import javax.servlet.http.HttpServletRequest;\n import javax.servlet.http.HttpServletResponse;\n import java.io.IOException;\n \n /**\n * Created by zj on 2017/4/10.\n */\n public class LoginServlet extends HttpServlet {\n /**\n * 跳转登录界面\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"user no login\");\n resp.sendRedirect(\"login.jsp\");\n }\n \n /**\n * 进行登录\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"登录\");\n String userName = req.getParameter(\"userName\");\n String password = req.getParameter(\"password\");\n \n Subject subject = SecurityUtils.getSubject();\n //创建token令牌, 用户/密码\n UsernamePasswordToken token = new UsernamePasswordToken(userName, password);\n try {\n //身份认证 登录\n subject.login(token);\n System.out.println(\"登录成功\");\n resp.sendRedirect(\"success.jsp\");\n } catch (Exception e) {\n System.out.println(\"账号密码不对\");\n e.printStackTrace();\n resp.sendRedirect(\"login.jsp\");\n }\n }\n }\n \n \n //******************************************************************\n login.jsp\n \n <%@ page language=\"java\" pageEncoding=\"UTF-8\" %>\n <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n \n \n \n

Hello World

\n 登录:\n
\n \n \n \n
\n \n \n\n\n\n> HomeServlet.java :登录成功以及退出登录地址\n\n\n package com.servlet;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.subject.Subject;\n \n import javax.servlet.ServletException;\n import javax.servlet.http.HttpServlet;\n import javax.servlet.http.HttpServletRequest;\n import javax.servlet.http.HttpServletResponse;\n import java.io.IOException;\n \n /**\n * Created by zj on 2017/4/10.\n */\n public class HomeServlet extends HttpServlet {\n /**\n * 进入主页(登陆成功界面)\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"主页 get\");\n \n req.getRequestDispatcher(\"success.jsp\").forward(req, resp);\n \n }\n \n /**\n * 用来退出登陆\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"主页 post\");\n \n System.out.println(\"login out\");\n \n //退出登录\n Subject subject = SecurityUtils.getSubject();\n subject.logout();\n \n resp.sendRedirect(\"login.jsp\");\n }\n }\n \n //******************************************************************\n success.jsp\n \n <%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>\n \n \n

Hello World!

成功!!!\n
\n \n
\n \n \n\n\n> AdminServlet.java\n\n \n package com.servlet;\n \n import javax.servlet.ServletException;\n import javax.servlet.http.HttpServlet;\n import javax.servlet.http.HttpServletRequest;\n import javax.servlet.http.HttpServletResponse;\n import java.io.IOException;\n \n /**\n * Created by zj on 2017/4/10.\n */\n public class AdminServlet extends HttpServlet {\n protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n System.out.println(\"ADMIN GET\");\n }\n \n protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n System.out.println(\"ADMIN POST\");\n }\n }\n\n\n#### 3.启动项目 \n\n##### 测试身份认证\n\n> 不登录情况下输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面\n\n{% qnimg 2.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n> 输入正确的用户名(ay2626)密码 点击登录 成功; 再次输入地址 http://localhost:8080/home 跳转到成功的jsp\n\n{% qnimg 3.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n> 点击退出登录 成功; 再次输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面\n\n##### 测试角色认证\n\n> 登录 :进行其他认证前先进行身份认证 使用ay2626用户登录(其角色只有student) 登录成功 跳转到success.jsp\n\n> 输入地址http://localhost:8080/admin 跳转到 http://localhost:8080/noAuth.jsp 表示没有权限访问此地址\n\n> 退出登录 用zj2626再次登录测试 登录成功 再次输入http://localhost:8080/admin 控制台打印\"ADMIN POST\" 表示访问成功\n\n{% qnimg 5.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n\n##### 测试权限认证\n\n> 可把配置文件中 /admin的过滤器改为 /admin=perms[\"student:create\"] 进行测试\n\n> 测试发现 有权限的ay2626用户可以访问而没有权限的zj2626不能访问\n\n---\n---\n\n#### 自定义Realm\n\n>实际开发中用户权限的配置要存放在数据库,so需要使用自定义realm来读取数据库中权限配置然后为用户赋予权限\n\n##### 测试开发步骤\n\n> 1.添加数据库依赖\n\n \n mysql\n mysql-connector-java\n 5.1.39\n compile\n \n \n\n> 2.设计构建测试的数据库表:有三个表:分别存储用户,角色,权限 并其他两个都与角色关联, 然后存入部分数据 ,再根据三个表建立对应实体(简单实体)\n\n{% qnimg 40.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n> 3.添加数据库操作类(写的并不完善 仅测试使用)\n\n \n package com.servlet;\n \n import java.sql.*;\n \n /**\n * Created by zj on 2017/4/11.\n */\n public class DBUtil {\n \n //获取数据库连接\n private static Connection getConnection() {\n try {\n Class.forName(\"com.mysql.jdbc.Driver\");\n \n Connection connection = DriverManager.getConnection(\"jdbc:mysql://127.0.0.1:3306/test\", \"root\", \"fangshuoit\");\n \n return connection;\n } catch (ClassNotFoundException | SQLException e) {\n e.printStackTrace();\n }\n return null;\n }\n \n /**\n * 通过用户名获取用户信息\n *\n * @param name\n * @return\n * @throws SQLException\n */\n public static User getByUserName(String name) throws SQLException {\n String sql = \"select * from ay_user where loginName = ?\";\n \n PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setString(1, name);\n \n ResultSet resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n return new User(resultSet.getInt(\"id\"), resultSet.getString(\"loginName\"), resultSet.getString(\"password\"));\n }\n \n return null;\n }\n \n /**\n * 通过用户名获取用户角色\n *\n * @param name\n * @return\n * @throws SQLException\n */\n public static Role getRolesByUserName(String name) throws SQLException {\n String sql = \"select roleId from ay_user where loginName = ?\";\n \n PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setString(1, name);\n \n ResultSet resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n Integer id = resultSet.getInt(\"roleId\");\n \n sql = \"select * from ay_role where id = ?\";\n preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setInt(1, id);\n resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n return new Role(resultSet.getInt(\"id\"), resultSet.getString(\"name\"));\n }\n }\n \n return null;\n }\n \n /**\n * 通过角色id获取角色权限\n *\n * @param roleId\n * @return\n * @throws SQLException\n */\n public static Perms getPermsByRole(Integer roleId) throws SQLException {\n String sql = \"select * from ay_perms where roleId = ?\";\n \n PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setInt(1, roleId);\n \n ResultSet resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n return new Perms(resultSet.getInt(\"id\"), resultSet.getString(\"name\"), resultSet.getInt(\"roleId\"));\n }\n \n return null;\n }\n }\n\n\n\n> 3.编写自定义的Realm类 需要AuthorizingRealm类并实现两个方法; 第一个是用来身份验证,第二是用来角色权限验证\n\n \n public class MyRealm extends AuthorizingRealm {\n \n /**\n * 验证当前登录的用户(身份认证), 不再需要在配置文件中配置用户的信息和其角色信息\n * \n * @param token 封装有用户的信息\n * @return\n * @throws AuthenticationException\n */\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n String userName = (String) token.getPrincipal();\n \n System.out.println(\"要登录的用户 : \" + userName);\n \n try {\n User user = DBUtil.getByUserName(userName);//这里只是通过用户名验证并获取用户信息,实际开发中需要用户名以及加密的密码\n if (user != null) {\n return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), \"XX\");//返回登录信息\n } else\n return null;\n } catch (SQLException e) {\n e.printStackTrace();\n }\n return null;\n }\n \n /**( 身份验证(登录)以后 )\n * 为当前用户授予角色和权限(根据登录的用户名,读取数据库中其角色和权限)\n *\n * @param principals 封装了身份信息\n * @return\n */\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {\n String userName = (String) principals.getPrimaryPrincipal();\n System.out.println(\"要权限的用户 : \" + userName);\n \n SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();\n \n try {\n Role role = DBUtil.getRolesByUserName(userName);\n if (null != role) {\n Set set = new HashSet<>();\n System.out.println(\"获得的角色: \"+ role.getName());\n set.add(role.getName());\n authenticationInfo.setRoles(set);//赋予角色\n \n Perms perms = DBUtil.getPermsByRole(role.getId());\n Set set2 = new HashSet<>();\n set2.add(perms.getName());\n System.out.println(\"获得的权限: \"+ perms.getName());\n authenticationInfo.setStringPermissions(set2);//赋予权限\n \n return authenticationInfo;\n }\n } catch (SQLException e) {\n \n }\n \n return null;\n }\n }\n\n\n\n> 4.修改配置文件shiro.ini ,引入自定义Realms,并去掉原来的 [users]和[roles]下的配置\n\n \n [main]\n authc.loginUrl=/login\n perms.unauthorizedUrl=/noAuth.jsp\n roles.unauthorizedUrl=/noAuth.jsp\n \n myRealm=com.servlet.MyRealm\n securityManager.realms=$myRealm\n [urls]\n /login=anon\n /home=authc\n /admin=roles[admin]\n\n\n##### *1.myRealm指向自定义Realm的位置*\n##### *2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用\",\"隔开*\n\n> 5.启动项目测试 发现:使用zj2626登录可以访问 /admin地址;而ay2626登录不能访问(没有user角色);而且每次请求都会进行认证(控制台打印信息)","source":"_posts/20170326_shiro.md","raw":"---\ntitle: Shiro的学习Helloworld\n\ncomments: true \n\ntags: \n - Shiro\n\ncategories: \n - 框架相关\n - 权限管理\n\ndescription: \n\n---\n\n## Apache Shiro是Java的一个安全框架\n### Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等\n\n{% qnimg d59f6d02-1f45-3285-8983-4ea5f18111d5.png title:图片标题 alt:图片说明 'class:class_name class_name2' extend:?imageView2/2/w/600 %}\n\n\n## Shiro基本功能\n>Authentication:身份认证/登录,验证用户是不是拥有相应的身份;\n\n>Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;\n\n>Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;\n\n>Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;\n\n>Web Support:Web支持,可以非常容易的集成到Web环境;\n\n>Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;\n\n>Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;\n\n>Testing:提供测试支持;\n\n>Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;\n\n>Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。\n\n\n\n## 1.身份认证\n \n 配置文件:\n ********************************\n [users]\n zj2626=123456\n ay2626=456789\n ********************************\n \n\n package com.em;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.UsernamePasswordToken;\n import org.apache.shiro.config.IniSecurityManagerFactory;\n import org.apache.shiro.mgt.SecurityManager;\n import org.apache.shiro.subject.Subject;\n import org.apache.shiro.util.Factory;\n \n public class Hello {\n \n public static void main(String args[]) {\n //初始化SecurityManager工厂 读取配置文件中的用户名密码\n Factory factory = new IniSecurityManagerFactory(\"classpath:shiro.ini\");\n //获取SecurityManager实例\n SecurityManager manager = factory.getInstance();\n //把SecurityManager实例绑定到SecurityUtils\n SecurityUtils.setSecurityManager(manager);\n //得到当前执行的用户\n Subject subject = SecurityUtils.getSubject();\n //创建token令牌, 用户/密码\n UsernamePasswordToken token = new UsernamePasswordToken(\"zj2626\", \"123456\");\n \n try {\n //身份认证 登录\n subject.login(token);\n \n System.out.println(\"登录成功\");\n } catch (Exception e) {\n System.out.println(\"登录失败\");\n e.printStackTrace();\n }\n \n subject.logout();\n }\n \n /*\n Subject: 认证主体\n Principals: 身份: 用户名\n Credentials: 凭证: 密码\n \n Realm: 域\n 1.jdbc realm | 2.jndi realm | 3.text realm\n \n */\n }\n\n\n\n从数据库中读取用户名密码 实现登录\n\n1.配置文件: jdbc_realm.ini (代码只需把读取的文件改成此文件即可测试使用)\n \n [main]\n jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm\n dataSource=com.mchange.v2.c3p0.ComboPooledDataSource\n dataSource.driverClass=com.mysql.jdbc.Driver\n dataSource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test\n dataSource.user=root\n dataSource.password=123456\n jdbcRealm.dataSource=$dataSource\n securityManager.realms=$jdbcRealm\n\n\n***\n***\n\n## 2.权限认证\n### 核心要素:(资源,)权限,角色,用户\n\n**用户--(分配)-->角色--(拥有)-->权限--(控制)---->资源**\n\n* 用户代表访问系统的用户,即subject。\n\n> **三种授权方式**\n\n \n 1. 编程式授权\n 1.1. 基于角色的访问控制\n 1.2. 基于权限的访问控制\n 2. 注解式授权\n 3.JSP标签授权\n\n \n---\n\n####步骤1: 封装一个工具类\n\n public class ShiroUtils {\n public static Subject login(String conf, String username, String passowrd) {\n //初始化SecurityManager工厂 conf是配置文件名称\n Factory factory = new IniSecurityManagerFactory(\"classpath:\" + conf + \".ini\");\n //获取SecurityManager实例\n SecurityManager manager = factory.getInstance();\n //把SecurityManager实例绑定到SecurityUtils\n SecurityUtils.setSecurityManager(manager);\n //得到当前执行的用户\n Subject subject = SecurityUtils.getSubject();\n //创建token令牌, 用户/密码\n UsernamePasswordToken token = new UsernamePasswordToken(username, passowrd);\n \n try {\n //身份认证 登录\n subject.login(token);\n } catch (Exception e) {\n e.printStackTrace();\n }\n \n return subject;\n }\n }\n\n\n####步骤2: 测试多种访问控制\n\n /*\n 基于角色的访问控制方式1\n \n 配置文件:shiro_role.ini\n ******************************\n [users]\n zj2626=123456,role1,role2\n py2626=123456,role1\n ay2626=456789,role3\n ******************************\n */\n @Test\n public void testHas() {\n Subject sub = ShiroUtils.login(\"shiro_config/shiro_role\", \"zj2626\", \"123456\");\n \n //判断有没有权限 返回布尔 表示验证的成功与否\n boolean bool = sub.hasRole(\"role1\");\n if (bool) {\n System.out.println(\"HAS\");\n }\n \n //判断有没有权限,一次多个分别判断 返回布尔数组\n boolean[] booleans = sub.hasRoles(Arrays.asList(\"role1\", \"role2\", \"role3\"));\n int i = 0;\n while (booleans[i]) {\n i++;\n \n if (booleans.length <= i) {\n break;\n }\n }\n \n //所有的角色都有才返回true\n System.out.println(sub.hasAllRoles(Arrays.asList(\"role1\", \"role2\", \"role3\")));\n \n //判断有没有权限 没有则抛异常\n sub.checkRole(\"role1\");\n sub.checkRole(\"role3\");\n \n //判断多个权限 有一个没有就抛异常 (2种参数形式)\n sub.checkRoles(Arrays.asList(\"role1\", \"role2\", \"role3\"));\n sub.checkRoles(\"role1\", \"role2\", \"role3\");\n \n //退出登陆\n sub.logout();\n }\n \n /*\n 基于权限的访问控制方式(过程同上)\n \n 配置文件:shiro_permision.ini\n ******************************\n [users]\n zj2626=123456,role1,role2\n py2626=123456,role1\n ay2626=456789,role3\n [roles]\n role1=user:select\n role2=user:add,user:update,user:delete\n ******************************\n */\n @Test\n public void testPermition() {\n Subject sub = ShiroUtils.login(\"shiro_config/shiro_permision\", \"py2626\", \"123456\");\n \n System.out.println(\"用户是否有权限 user:select:\" + sub.isPermitted(\"user:select\")); //true\n System.out.println(\"用户是否有权限 user:update:\" + sub.isPermitted(\"user:update\")); //false\n \n boolean[] booleans = sub.isPermitted(\"user:add\", \"user:select\");\n System.out.println(booleans[0] + \"____\" + booleans[1]);\n \n System.out.println(sub.isPermittedAll(\"user:add\", \"user:select\"));\n \n //没有会抛出异常\n sub.checkPermission(\"user:select\");\n sub.checkPermissions(\"user:select\", \"user:update\");\n \n sub.logout();\n }\n\n***\n***\n\n## 3.集成web进行测试\n\n#### 1.新建一个maven的web项目\n\n{% qnimg 1.png title:项目目录文件 alt:项目目录文件 extend:?imageView2/2/w/600 %}\n\n> web.xml 配置shiro的必须的配置: 监听器,过滤器\n\n\n \n \n Archetype Created Web Application\n \n \n \n loginServlet\n com.servlet.LoginServlet\n \n \n loginServlet\n /login\n \n \n homeServlet\n com.servlet.HomeServlet\n \n \n homeServlet\n /home\n \n \n adminServlet\n com.servlet.AdminServlet\n \n \n adminServlet\n /admin\n \n \n \n \n org.apache.shiro.web.env.EnvironmentLoaderListener\n \n \n \n \n ShiroFilter\n org.apache.shiro.web.servlet.ShiroFilter\n \n config\n classpath:shiro.ini\n \n \n \n ShiroFilter\n /*\n \n \n \n\n\n\n> pom.xml :所需依赖\n\n \n \n org.apache.shiro\n shiro-web\n 1.3.2\n \n \n org.apache.shiro\n shiro-core\n 1.3.2\n \n \n org.apache.shiro\n shiro-spring\n 1.3.2\n \n \n org.apache.tomcat\n tomcat-servlet-api\n 8.5.4\n provided\n \n \n org.slf4j\n slf4j-log4j12\n 1.7.7\n compile\n \n \n commons-logging\n commons-logging\n 1.2\n \n \n commons-beanutils\n commons-beanutils\n 1.9.3\n \n \n commons-collections\n commons-collections\n 3.2.1\n \n \n jstl\n jstl\n 1.2\n \n \n\n> shiro.ini: 权限配置文件,配置什么用户有什么角色,什么角色有什么权限\n\n [main]\n authc.loginUrl=/login\n perms.unauthorizedUrl=/noAuth.jsp\n roles.unauthorizedUrl=/noAuth.jsp\n [users]\n zj2626=123456,admin\n ay2626=456789,student\n [roles]\n admin=user:*,student:select\n student:student:*\n [urls]\n /login=anon\n /home=authc\n /admin=roles[admin]\n\n##### *1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址...(loginUrl是authc的一个属性)*\n##### *2.roles.unauthorizeUrl配置角色认证不通过跳转的地址...(noAuth.jsp页面目前只有一行字)*\n##### *3.perms.unauthorizeUrl配置权限认证不通过跳转的地址*\n\n##### *4.[users]下配置用户身份信息以及用户角色*\n##### *5.[roles]下配置角色以及角色的控制权限*\n##### *6.[urls]下配置访问地址所需的权限, 其中值为\"anon过滤器\"表示地址不需要登录即可访问; \"authc过滤器\"表示地址登录才能访问*\n##### *7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围*\n##### *8.值为 perms[\"student:create\"] 表示 必须有权限为\"student:create\"的用户才能范围*\n##### *9.多个过滤器用\",\"隔开 而且相互为\"且\"的关系(必须同时满足才能访问)*\n##### *10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef.....)*\n##### *11.地址可以使用*表示匹配任意个任意字符(eg: /home*=authc 表示可过滤 /home123; /homeef.....)*\n##### *12.地址可以使用**表示匹配多路径(eg: /home/**=authc 表示可过滤 /home/abc; /home/aaa/bbb.....)*\n\n#### 2.编写sevlet代码\n\n> LoginServlet.java :身份验证地址\n\n \n package com.servlet;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.authc.UsernamePasswordToken;\n import org.apache.shiro.subject.Subject;\n \n import javax.servlet.ServletException;\n import javax.servlet.http.HttpServlet;\n import javax.servlet.http.HttpServletRequest;\n import javax.servlet.http.HttpServletResponse;\n import java.io.IOException;\n \n /**\n * Created by zj on 2017/4/10.\n */\n public class LoginServlet extends HttpServlet {\n /**\n * 跳转登录界面\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"user no login\");\n resp.sendRedirect(\"login.jsp\");\n }\n \n /**\n * 进行登录\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"登录\");\n String userName = req.getParameter(\"userName\");\n String password = req.getParameter(\"password\");\n \n Subject subject = SecurityUtils.getSubject();\n //创建token令牌, 用户/密码\n UsernamePasswordToken token = new UsernamePasswordToken(userName, password);\n try {\n //身份认证 登录\n subject.login(token);\n System.out.println(\"登录成功\");\n resp.sendRedirect(\"success.jsp\");\n } catch (Exception e) {\n System.out.println(\"账号密码不对\");\n e.printStackTrace();\n resp.sendRedirect(\"login.jsp\");\n }\n }\n }\n \n \n //******************************************************************\n login.jsp\n \n <%@ page language=\"java\" pageEncoding=\"UTF-8\" %>\n <%@ taglib uri=\"http://java.sun.com/jsp/jstl/core\" prefix=\"c\" %>\n \n \n \n

Hello World

\n 登录:\n
\n \n \n \n
\n \n \n\n\n\n> HomeServlet.java :登录成功以及退出登录地址\n\n\n package com.servlet;\n \n import org.apache.shiro.SecurityUtils;\n import org.apache.shiro.subject.Subject;\n \n import javax.servlet.ServletException;\n import javax.servlet.http.HttpServlet;\n import javax.servlet.http.HttpServletRequest;\n import javax.servlet.http.HttpServletResponse;\n import java.io.IOException;\n \n /**\n * Created by zj on 2017/4/10.\n */\n public class HomeServlet extends HttpServlet {\n /**\n * 进入主页(登陆成功界面)\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"主页 get\");\n \n req.getRequestDispatcher(\"success.jsp\").forward(req, resp);\n \n }\n \n /**\n * 用来退出登陆\n *\n * @param req\n * @param resp\n * @throws ServletException\n * @throws IOException\n */\n @Override\n protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n System.out.println(\"主页 post\");\n \n System.out.println(\"login out\");\n \n //退出登录\n Subject subject = SecurityUtils.getSubject();\n subject.logout();\n \n resp.sendRedirect(\"login.jsp\");\n }\n }\n \n //******************************************************************\n success.jsp\n \n <%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>\n \n \n

Hello World!

成功!!!\n
\n \n
\n \n \n\n\n> AdminServlet.java\n\n \n package com.servlet;\n \n import javax.servlet.ServletException;\n import javax.servlet.http.HttpServlet;\n import javax.servlet.http.HttpServletRequest;\n import javax.servlet.http.HttpServletResponse;\n import java.io.IOException;\n \n /**\n * Created by zj on 2017/4/10.\n */\n public class AdminServlet extends HttpServlet {\n protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n System.out.println(\"ADMIN GET\");\n }\n \n protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n System.out.println(\"ADMIN POST\");\n }\n }\n\n\n#### 3.启动项目 \n\n##### 测试身份认证\n\n> 不登录情况下输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面\n\n{% qnimg 2.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n> 输入正确的用户名(ay2626)密码 点击登录 成功; 再次输入地址 http://localhost:8080/home 跳转到成功的jsp\n\n{% qnimg 3.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n> 点击退出登录 成功; 再次输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面\n\n##### 测试角色认证\n\n> 登录 :进行其他认证前先进行身份认证 使用ay2626用户登录(其角色只有student) 登录成功 跳转到success.jsp\n\n> 输入地址http://localhost:8080/admin 跳转到 http://localhost:8080/noAuth.jsp 表示没有权限访问此地址\n\n> 退出登录 用zj2626再次登录测试 登录成功 再次输入http://localhost:8080/admin 控制台打印\"ADMIN POST\" 表示访问成功\n\n{% qnimg 5.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n\n##### 测试权限认证\n\n> 可把配置文件中 /admin的过滤器改为 /admin=perms[\"student:create\"] 进行测试\n\n> 测试发现 有权限的ay2626用户可以访问而没有权限的zj2626不能访问\n\n---\n---\n\n#### 自定义Realm\n\n>实际开发中用户权限的配置要存放在数据库,so需要使用自定义realm来读取数据库中权限配置然后为用户赋予权限\n\n##### 测试开发步骤\n\n> 1.添加数据库依赖\n\n \n mysql\n mysql-connector-java\n 5.1.39\n compile\n \n \n\n> 2.设计构建测试的数据库表:有三个表:分别存储用户,角色,权限 并其他两个都与角色关联, 然后存入部分数据 ,再根据三个表建立对应实体(简单实体)\n\n{% qnimg 40.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n> 3.添加数据库操作类(写的并不完善 仅测试使用)\n\n \n package com.servlet;\n \n import java.sql.*;\n \n /**\n * Created by zj on 2017/4/11.\n */\n public class DBUtil {\n \n //获取数据库连接\n private static Connection getConnection() {\n try {\n Class.forName(\"com.mysql.jdbc.Driver\");\n \n Connection connection = DriverManager.getConnection(\"jdbc:mysql://127.0.0.1:3306/test\", \"root\", \"fangshuoit\");\n \n return connection;\n } catch (ClassNotFoundException | SQLException e) {\n e.printStackTrace();\n }\n return null;\n }\n \n /**\n * 通过用户名获取用户信息\n *\n * @param name\n * @return\n * @throws SQLException\n */\n public static User getByUserName(String name) throws SQLException {\n String sql = \"select * from ay_user where loginName = ?\";\n \n PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setString(1, name);\n \n ResultSet resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n return new User(resultSet.getInt(\"id\"), resultSet.getString(\"loginName\"), resultSet.getString(\"password\"));\n }\n \n return null;\n }\n \n /**\n * 通过用户名获取用户角色\n *\n * @param name\n * @return\n * @throws SQLException\n */\n public static Role getRolesByUserName(String name) throws SQLException {\n String sql = \"select roleId from ay_user where loginName = ?\";\n \n PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setString(1, name);\n \n ResultSet resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n Integer id = resultSet.getInt(\"roleId\");\n \n sql = \"select * from ay_role where id = ?\";\n preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setInt(1, id);\n resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n return new Role(resultSet.getInt(\"id\"), resultSet.getString(\"name\"));\n }\n }\n \n return null;\n }\n \n /**\n * 通过角色id获取角色权限\n *\n * @param roleId\n * @return\n * @throws SQLException\n */\n public static Perms getPermsByRole(Integer roleId) throws SQLException {\n String sql = \"select * from ay_perms where roleId = ?\";\n \n PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n preparedStatement.setInt(1, roleId);\n \n ResultSet resultSet = preparedStatement.executeQuery();\n if (resultSet.next()) {\n return new Perms(resultSet.getInt(\"id\"), resultSet.getString(\"name\"), resultSet.getInt(\"roleId\"));\n }\n \n return null;\n }\n }\n\n\n\n> 3.编写自定义的Realm类 需要AuthorizingRealm类并实现两个方法; 第一个是用来身份验证,第二是用来角色权限验证\n\n \n public class MyRealm extends AuthorizingRealm {\n \n /**\n * 验证当前登录的用户(身份认证), 不再需要在配置文件中配置用户的信息和其角色信息\n * \n * @param token 封装有用户的信息\n * @return\n * @throws AuthenticationException\n */\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n String userName = (String) token.getPrincipal();\n \n System.out.println(\"要登录的用户 : \" + userName);\n \n try {\n User user = DBUtil.getByUserName(userName);//这里只是通过用户名验证并获取用户信息,实际开发中需要用户名以及加密的密码\n if (user != null) {\n return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), \"XX\");//返回登录信息\n } else\n return null;\n } catch (SQLException e) {\n e.printStackTrace();\n }\n return null;\n }\n \n /**( 身份验证(登录)以后 )\n * 为当前用户授予角色和权限(根据登录的用户名,读取数据库中其角色和权限)\n *\n * @param principals 封装了身份信息\n * @return\n */\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {\n String userName = (String) principals.getPrimaryPrincipal();\n System.out.println(\"要权限的用户 : \" + userName);\n \n SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();\n \n try {\n Role role = DBUtil.getRolesByUserName(userName);\n if (null != role) {\n Set set = new HashSet<>();\n System.out.println(\"获得的角色: \"+ role.getName());\n set.add(role.getName());\n authenticationInfo.setRoles(set);//赋予角色\n \n Perms perms = DBUtil.getPermsByRole(role.getId());\n Set set2 = new HashSet<>();\n set2.add(perms.getName());\n System.out.println(\"获得的权限: \"+ perms.getName());\n authenticationInfo.setStringPermissions(set2);//赋予权限\n \n return authenticationInfo;\n }\n } catch (SQLException e) {\n \n }\n \n return null;\n }\n }\n\n\n\n> 4.修改配置文件shiro.ini ,引入自定义Realms,并去掉原来的 [users]和[roles]下的配置\n\n \n [main]\n authc.loginUrl=/login\n perms.unauthorizedUrl=/noAuth.jsp\n roles.unauthorizedUrl=/noAuth.jsp\n \n myRealm=com.servlet.MyRealm\n securityManager.realms=$myRealm\n [urls]\n /login=anon\n /home=authc\n /admin=roles[admin]\n\n\n##### *1.myRealm指向自定义Realm的位置*\n##### *2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用\",\"隔开*\n\n> 5.启动项目测试 发现:使用zj2626登录可以访问 /admin地址;而ay2626登录不能访问(没有user角色);而且每次请求都会进行认证(控制台打印信息)","slug":"20170326_shiro","published":1,"date":"2020-01-15T05:50:39.606Z","updated":"2018-01-13T02:29:22.156Z","layout":"post","photos":[],"link":"","_id":"ckm3invhg009l24ujiejy0cmb","content":"

Apache Shiro是Java的一个安全框架

Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等

\"图片说明\"\n

Shiro基本功能

\n

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

\n
\n
\n

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

\n
\n
\n

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

\n
\n
\n

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

\n
\n
\n

Web Support:Web支持,可以非常容易的集成到Web环境;

\n
\n
\n

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

\n
\n
\n

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

\n
\n
\n

Testing:提供测试支持;

\n
\n
\n

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

\n
\n
\n

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

\n
\n\n

1.身份认证

配置文件:\n    ********************************\n    [users]\n    zj2626=123456\n    ay2626=456789\n    ********************************\n\n\npackage com.em;\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.authc.UsernamePasswordToken;\nimport org.apache.shiro.config.IniSecurityManagerFactory;\nimport org.apache.shiro.mgt.SecurityManager;\nimport org.apache.shiro.subject.Subject;\nimport org.apache.shiro.util.Factory;\n\npublic class Hello {\n\n    public static void main(String args[]) {\n        //初始化SecurityManager工厂      读取配置文件中的用户名密码\n        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");\n        //获取SecurityManager实例\n        SecurityManager manager = factory.getInstance();\n        //把SecurityManager实例绑定到SecurityUtils\n        SecurityUtils.setSecurityManager(manager);\n        //得到当前执行的用户\n        Subject subject = SecurityUtils.getSubject();\n        //创建token令牌, 用户/密码\n        UsernamePasswordToken token = new UsernamePasswordToken("zj2626", "123456");\n\n        try {\n            //身份认证 登录\n            subject.login(token);\n\n            System.out.println("登录成功");\n        } catch (Exception e) {\n            System.out.println("登录失败");\n            e.printStackTrace();\n        }\n\n        subject.logout();\n    }\n\n    /*\n        Subject: 认证主体\n        Principals:  身份: 用户名\n        Credentials: 凭证: 密码\n\n        Realm: 域\n            1.jdbc realm | 2.jndi realm | 3.text realm\n\n     */\n}\n

从数据库中读取用户名密码 实现登录

\n

1.配置文件: jdbc_realm.ini (代码只需把读取的文件改成此文件即可测试使用)

\n
[main]\njdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm\ndataSource=com.mchange.v2.c3p0.ComboPooledDataSource\ndataSource.driverClass=com.mysql.jdbc.Driver\ndataSource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test\ndataSource.user=root\ndataSource.password=123456\njdbcRealm.dataSource=$dataSource\nsecurityManager.realms=$jdbcRealm\n

\n
\n

2.权限认证

核心要素:(资源,)权限,角色,用户

用户–(分配)–>角色–(拥有)–>权限–(控制)—->资源

\n
    \n
  • 用户代表访问系统的用户,即subject。
  • \n
\n
\n

三种授权方式

\n
\n
1. 编程式授权\n    1.1. 基于角色的访问控制\n    1.2. 基于权限的访问控制\n2. 注解式授权\n3.JSP标签授权\n

\n

####步骤1: 封装一个工具类

\n
public class ShiroUtils {\n    public static Subject login(String conf, String username, String passowrd) {\n        //初始化SecurityManager工厂 conf是配置文件名称\n        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:" + conf + ".ini");\n        //获取SecurityManager实例\n        SecurityManager manager = factory.getInstance();\n        //把SecurityManager实例绑定到SecurityUtils\n        SecurityUtils.setSecurityManager(manager);\n        //得到当前执行的用户\n        Subject subject = SecurityUtils.getSubject();\n        //创建token令牌, 用户/密码\n        UsernamePasswordToken token = new UsernamePasswordToken(username, passowrd);\n\n        try {\n            //身份认证 登录\n            subject.login(token);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return subject;\n    }\n}\n

####步骤2: 测试多种访问控制

\n
/*\n    基于角色的访问控制方式1\n\n    配置文件:shiro_role.ini\n    ******************************\n    [users]\n    zj2626=123456,role1,role2\n    py2626=123456,role1\n    ay2626=456789,role3\n    ******************************\n     */\n    @Test\n    public void testHas() {\n        Subject sub = ShiroUtils.login("shiro_config/shiro_role", "zj2626", "123456");\n\n        //判断有没有权限 返回布尔 表示验证的成功与否\n        boolean bool = sub.hasRole("role1");\n        if (bool) {\n            System.out.println("HAS");\n        }\n\n        //判断有没有权限,一次多个分别判断 返回布尔数组\n        boolean[] booleans = sub.hasRoles(Arrays.asList("role1", "role2", "role3"));\n        int i = 0;\n        while (booleans[i]) {\n            i++;\n\n            if (booleans.length <= i) {\n                break;\n            }\n        }\n\n        //所有的角色都有才返回true\n        System.out.println(sub.hasAllRoles(Arrays.asList("role1", "role2", "role3")));\n\n        //判断有没有权限 没有则抛异常\n        sub.checkRole("role1");\n        sub.checkRole("role3");\n\n        //判断多个权限 有一个没有就抛异常 (2种参数形式)\n        sub.checkRoles(Arrays.asList("role1", "role2", "role3"));\n        sub.checkRoles("role1", "role2", "role3");\n\n        //退出登陆\n        sub.logout();\n    }\n\n    /*\n    基于权限的访问控制方式(过程同上)\n\n    配置文件:shiro_permision.ini\n    ******************************\n    [users]\n    zj2626=123456,role1,role2\n    py2626=123456,role1\n    ay2626=456789,role3\n    [roles]\n    role1=user:select\n    role2=user:add,user:update,user:delete\n    ******************************\n     */\n    @Test\n    public void testPermition() {\n        Subject sub = ShiroUtils.login("shiro_config/shiro_permision", "py2626", "123456");\n\n        System.out.println("用户是否有权限 user:select:" + sub.isPermitted("user:select"));    //true\n        System.out.println("用户是否有权限 user:update:" + sub.isPermitted("user:update"));    //false\n\n        boolean[] booleans = sub.isPermitted("user:add", "user:select");\n        System.out.println(booleans[0] + "____" + booleans[1]);\n\n        System.out.println(sub.isPermittedAll("user:add", "user:select"));\n\n        //没有会抛出异常\n        sub.checkPermission("user:select");\n        sub.checkPermissions("user:select", "user:update");\n\n        sub.logout();\n    }\n

\n
\n

3.集成web进行测试

1.新建一个maven的web项目

\"项目目录文件\"\n
\n

web.xml 配置shiro的必须的配置: 监听器,过滤器

\n
\n
<?xml version="1.0" encoding="UTF-8"?>\n<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"\n         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"\n         version="3.1">\n    <display-name>Archetype Created Web Application</display-name>\n\n    <!--三个servlet配置   /login跳转到登陆页面  /home跳转到主页,即登陆成功页面 /admin用来测试角色和权限-->\n    <servlet>\n        <servlet-name>loginServlet</servlet-name>\n        <servlet-class>com.servlet.LoginServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>loginServlet</servlet-name>\n        <url-pattern>/login</url-pattern>\n    </servlet-mapping>\n    <servlet>\n        <servlet-name>homeServlet</servlet-name>\n        <servlet-class>com.servlet.HomeServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>homeServlet</servlet-name>\n        <url-pattern>/home</url-pattern>\n    </servlet-mapping>\n    <servlet>\n        <servlet-name>adminServlet</servlet-name>\n        <servlet-class>com.servlet.AdminServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>adminServlet</servlet-name>\n        <url-pattern>/admin</url-pattern>\n    </servlet-mapping>\n\n    <!--shiro监听-->\n    <listener>\n        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>\n    </listener>\n\n    <!--shiro过滤器 这里过滤所有的地址 并且指定权限配置文件(一般项目中权限的配置存放在数据库中)-->\n    <filter>\n        <filter-name>ShiroFilter</filter-name>\n        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>\n        <init-param>\n            <param-name>config</param-name>\n            <param-value>classpath:shiro.ini</param-value>\n        </init-param>\n    </filter>\n    <filter-mapping>\n        <filter-name>ShiroFilter</filter-name>\n        <url-pattern>/*</url-pattern>\n    </filter-mapping>\n\n</web-app>\n
\n

pom.xml :所需依赖

\n
\n
<dependencies>\n    <dependency>\n        <groupId>org.apache.shiro</groupId>\n        <artifactId>shiro-web</artifactId>\n        <version>1.3.2</version>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.shiro</groupId>\n        <artifactId>shiro-core</artifactId>\n        <version>1.3.2</version>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.shiro</groupId>\n        <artifactId>shiro-spring</artifactId>\n        <version>1.3.2</version>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.tomcat</groupId>\n        <artifactId>tomcat-servlet-api</artifactId>\n        <version>8.5.4</version>\n        <scope>provided</scope>\n    </dependency>\n    <dependency>\n        <groupId>org.slf4j</groupId>\n        <artifactId>slf4j-log4j12</artifactId>\n        <version>1.7.7</version>\n        <scope>compile</scope>\n    </dependency>\n    <dependency>\n        <groupId>commons-logging</groupId>\n        <artifactId>commons-logging</artifactId>\n        <version>1.2</version>\n    </dependency>\n    <dependency>\n        <groupId>commons-beanutils</groupId>\n        <artifactId>commons-beanutils</artifactId>\n        <version>1.9.3</version>\n    </dependency>\n    <dependency>\n        <groupId>commons-collections</groupId>\n        <artifactId>commons-collections</artifactId>\n        <version>3.2.1</version>\n    </dependency>\n    <dependency>\n        <groupId>jstl</groupId>\n        <artifactId>jstl</artifactId>\n        <version>1.2</version>\n    </dependency>\n</dependencies>\n
\n

shiro.ini: 权限配置文件,配置什么用户有什么角色,什么角色有什么权限

\n
\n
[main]\nauthc.loginUrl=/login\nperms.unauthorizedUrl=/noAuth.jsp\nroles.unauthorizedUrl=/noAuth.jsp\n[users]\nzj2626=123456,admin\nay2626=456789,student\n[roles]\nadmin=user:*,student:select\nstudent:student:*\n[urls]\n/login=anon\n/home=authc\n/admin=roles[admin]\n
1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址…(loginUrl是authc的一个属性)
2.roles.unauthorizeUrl配置角色认证不通过跳转的地址…(noAuth.jsp页面目前只有一行字)
3.perms.unauthorizeUrl配置权限认证不通过跳转的地址
4.[users]下配置用户身份信息以及用户角色
5.[roles]下配置角色以及角色的控制权限
6.[urls]下配置访问地址所需的权限, 其中值为”anon过滤器”表示地址不需要登录即可访问; “authc过滤器”表示地址登录才能访问
7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围
8.值为 perms[“student:create”] 表示 必须有权限为”student:create”的用户才能范围
9.多个过滤器用”,”隔开 而且相互为”且”的关系(必须同时满足才能访问)
10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef…..)
11.地址可以使用表示匹配任意个任意字符(eg: /home=authc 表示可过滤 /home123; /homeef…..)
12.地址可以使用表示匹配多路径(eg: /home/=authc 表示可过滤 /home/abc; /home/aaa/bbb…..)

2.编写sevlet代码

\n

LoginServlet.java :身份验证地址

\n
\n
package com.servlet;\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.authc.UsernamePasswordToken;\nimport org.apache.shiro.subject.Subject;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Created by zj on 2017/4/10.\n */\npublic class LoginServlet extends HttpServlet {\n    /**\n     * 跳转登录界面\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("user no login");\n        resp.sendRedirect("login.jsp");\n    }\n\n    /**\n     * 进行登录\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("登录");\n        String userName = req.getParameter("userName");\n        String password = req.getParameter("password");\n\n        Subject subject = SecurityUtils.getSubject();\n        //创建token令牌, 用户/密码\n        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);\n        try {\n            //身份认证 登录\n            subject.login(token);\n            System.out.println("登录成功");\n            resp.sendRedirect("success.jsp");\n        } catch (Exception e) {\n            System.out.println("账号密码不对");\n            e.printStackTrace();\n            resp.sendRedirect("login.jsp");\n        }\n    }\n}\n\n\n//******************************************************************\nlogin.jsp\n\n<%@ page language="java" pageEncoding="UTF-8" %>\n<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>\n<!DOCTYPE HTML>\n<html>\n<body>\n<h2>Hello World</h2>\n登录:\n<form action="/login" method="post">\n    <input type="text" value="" name="userName">\n    <input type="password" value="" name="password">\n    <input type="submit" value="登录">\n</form>\n</body>\n</html>\n
\n

HomeServlet.java :登录成功以及退出登录地址

\n
\n
package com.servlet;\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.subject.Subject;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Created by zj on 2017/4/10.\n */\npublic class HomeServlet extends HttpServlet {\n    /**\n     * 进入主页(登陆成功界面)\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("主页 get");\n\n        req.getRequestDispatcher("success.jsp").forward(req, resp);\n\n    }\n\n    /**\n     * 用来退出登陆\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("主页 post");\n\n        System.out.println("login out");\n\n        //退出登录\n        Subject subject = SecurityUtils.getSubject();\n        subject.logout();\n\n        resp.sendRedirect("login.jsp");\n    }\n}\n\n//******************************************************************\nsuccess.jsp\n\n<%@ page contentType="text/html;charset=UTF-8" language="java" %>\n<html>\n<body>\n<h2>Hello World!</h2> 成功!!!\n<form action="/home" method="post">\n    <input type="submit" value="退出登陆">\n</form>\n</body>\n</html>\n
\n

AdminServlet.java

\n
\n
package com.servlet;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Created by zj on 2017/4/10.\n */\npublic class AdminServlet extends HttpServlet {\n    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n        System.out.println("ADMIN GET");\n    }\n\n    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n        System.out.println("ADMIN POST");\n    }\n}\n

3.启动项目

测试身份认证
\n

不登录情况下输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面

\n
\n\"图片说明\"\n
\n

输入正确的用户名(ay2626)密码 点击登录 成功; 再次输入地址 http://localhost:8080/home 跳转到成功的jsp

\n
\n\"图片说明\"\n
\n

点击退出登录 成功; 再次输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面

\n
\n
测试角色认证
\n

登录 :进行其他认证前先进行身份认证 使用ay2626用户登录(其角色只有student) 登录成功 跳转到success.jsp

\n
\n
\n

输入地址http://localhost:8080/admin 跳转到 http://localhost:8080/noAuth.jsp 表示没有权限访问此地址

\n
\n
\n

退出登录 用zj2626再次登录测试 登录成功 再次输入http://localhost:8080/admin 控制台打印”ADMIN POST” 表示访问成功

\n
\n\"图片说明\"\n
测试权限认证
\n

可把配置文件中 /admin的过滤器改为 /admin=perms[“student:create”] 进行测试

\n
\n
\n

测试发现 有权限的ay2626用户可以访问而没有权限的zj2626不能访问

\n
\n
\n
\n

自定义Realm

\n

实际开发中用户权限的配置要存放在数据库,so需要使用自定义realm来读取数据库中权限配置然后为用户赋予权限

\n
\n
测试开发步骤
\n

1.添加数据库依赖

\n
\n
<dependency>\n    <groupId>mysql</groupId>\n    <artifactId>mysql-connector-java</artifactId>\n    <version>5.1.39</version>\n    <scope>compile</scope>\n</dependency>\n
\n

2.设计构建测试的数据库表:有三个表:分别存储用户,角色,权限 并其他两个都与角色关联, 然后存入部分数据 ,再根据三个表建立对应实体(简单实体)

\n
\n\"图片说明\"\n
\n

3.添加数据库操作类(写的并不完善 仅测试使用)

\n
\n
package com.servlet;\n\nimport java.sql.*;\n\n/**\n * Created by zj on 2017/4/11.\n */\npublic class DBUtil {\n\n    //获取数据库连接\n    private static Connection getConnection() {\n        try {\n            Class.forName("com.mysql.jdbc.Driver");\n\n            Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "fangshuoit");\n\n            return connection;\n        } catch (ClassNotFoundException | SQLException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * 通过用户名获取用户信息\n     *\n     * @param name\n     * @return\n     * @throws SQLException\n     */\n    public static User getByUserName(String name) throws SQLException {\n        String sql = "select * from ay_user where loginName = ?";\n\n        PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n        preparedStatement.setString(1, name);\n\n        ResultSet resultSet = preparedStatement.executeQuery();\n        if (resultSet.next()) {\n            return new User(resultSet.getInt("id"), resultSet.getString("loginName"), resultSet.getString("password"));\n        }\n\n        return null;\n    }\n\n    /**\n     * 通过用户名获取用户角色\n     *\n     * @param name\n     * @return\n     * @throws SQLException\n     */\n    public static Role getRolesByUserName(String name) throws SQLException {\n        String sql = "select roleId from ay_user where loginName = ?";\n\n        PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n        preparedStatement.setString(1, name);\n\n        ResultSet resultSet = preparedStatement.executeQuery();\n        if (resultSet.next()) {\n            Integer id = resultSet.getInt("roleId");\n\n            sql = "select * from ay_role where id = ?";\n            preparedStatement = getConnection().prepareStatement(sql);\n            preparedStatement.setInt(1, id);\n            resultSet = preparedStatement.executeQuery();\n            if (resultSet.next()) {\n                return new Role(resultSet.getInt("id"), resultSet.getString("name"));\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * 通过角色id获取角色权限\n     *\n     * @param roleId\n     * @return\n     * @throws SQLException\n     */\n    public static Perms getPermsByRole(Integer roleId) throws SQLException {\n        String sql = "select * from ay_perms where roleId = ?";\n\n        PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n        preparedStatement.setInt(1, roleId);\n\n        ResultSet resultSet = preparedStatement.executeQuery();\n        if (resultSet.next()) {\n            return new Perms(resultSet.getInt("id"), resultSet.getString("name"), resultSet.getInt("roleId"));\n        }\n\n        return null;\n    }\n}\n
\n

3.编写自定义的Realm类 需要AuthorizingRealm类并实现两个方法; 第一个是用来身份验证,第二是用来角色权限验证

\n
\n
public class MyRealm extends AuthorizingRealm {\n\n    /**\n     * 验证当前登录的用户(身份认证), 不再需要在配置文件中配置用户的信息和其角色信息\n     * \n     * @param token 封装有用户的信息\n     * @return\n     * @throws AuthenticationException\n     */\n    @Override\n    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n        String userName = (String) token.getPrincipal();\n\n        System.out.println("要登录的用户 : " + userName);\n\n        try {\n            User user = DBUtil.getByUserName(userName);//这里只是通过用户名验证并获取用户信息,实际开发中需要用户名以及加密的密码\n            if (user != null) {\n                return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), "XX");//返回登录信息\n            } else\n                return null;\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**( 身份验证(登录)以后 )\n     * 为当前用户授予角色和权限(根据登录的用户名,读取数据库中其角色和权限)\n     *\n     * @param principals 封装了身份信息\n     * @return\n     */\n    @Override\n    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {\n        String userName = (String) principals.getPrimaryPrincipal();\n        System.out.println("要权限的用户 : " + userName);\n\n        SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();\n\n        try {\n            Role role = DBUtil.getRolesByUserName(userName);\n            if (null != role) {\n                Set<String> set = new HashSet<>();\n                System.out.println("获得的角色: "+ role.getName());\n                set.add(role.getName());\n                authenticationInfo.setRoles(set);//赋予角色\n\n                Perms perms = DBUtil.getPermsByRole(role.getId());\n                Set<String> set2 = new HashSet<>();\n                set2.add(perms.getName());\n                System.out.println("获得的权限: "+ perms.getName());\n                authenticationInfo.setStringPermissions(set2);//赋予权限\n\n                return authenticationInfo;\n            }\n        } catch (SQLException e) {\n\n        }\n\n        return null;\n    }\n}\n
\n

4.修改配置文件shiro.ini ,引入自定义Realms,并去掉原来的 [users]和[roles]下的配置

\n
\n
[main]\nauthc.loginUrl=/login\nperms.unauthorizedUrl=/noAuth.jsp\nroles.unauthorizedUrl=/noAuth.jsp\n\nmyRealm=com.servlet.MyRealm\nsecurityManager.realms=$myRealm\n[urls]\n/login=anon\n/home=authc\n/admin=roles[admin]\n
1.myRealm指向自定义Realm的位置
2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用”,”隔开
\n

5.启动项目测试 发现:使用zj2626登录可以访问 /admin地址;而ay2626登录不能访问(没有user角色);而且每次请求都会进行认证(控制台打印信息)

\n
\n","site":{"data":{}},"excerpt":"

Apache Shiro是Java的一个安全框架

Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等

\"图片说明\"\n

Shiro基本功能

\n

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

\n
\n
\n

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

\n
\n
\n

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

\n
\n
\n

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

\n
\n
\n

Web Support:Web支持,可以非常容易的集成到Web环境;

\n
\n
\n

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

\n
\n
\n

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

\n
\n
\n

Testing:提供测试支持;

\n
\n
\n

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

\n
\n
\n

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

\n
","more":"

1.身份认证

配置文件:\n    ********************************\n    [users]\n    zj2626=123456\n    ay2626=456789\n    ********************************\n\n\npackage com.em;\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.authc.UsernamePasswordToken;\nimport org.apache.shiro.config.IniSecurityManagerFactory;\nimport org.apache.shiro.mgt.SecurityManager;\nimport org.apache.shiro.subject.Subject;\nimport org.apache.shiro.util.Factory;\n\npublic class Hello {\n\n    public static void main(String args[]) {\n        //初始化SecurityManager工厂      读取配置文件中的用户名密码\n        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");\n        //获取SecurityManager实例\n        SecurityManager manager = factory.getInstance();\n        //把SecurityManager实例绑定到SecurityUtils\n        SecurityUtils.setSecurityManager(manager);\n        //得到当前执行的用户\n        Subject subject = SecurityUtils.getSubject();\n        //创建token令牌, 用户/密码\n        UsernamePasswordToken token = new UsernamePasswordToken("zj2626", "123456");\n\n        try {\n            //身份认证 登录\n            subject.login(token);\n\n            System.out.println("登录成功");\n        } catch (Exception e) {\n            System.out.println("登录失败");\n            e.printStackTrace();\n        }\n\n        subject.logout();\n    }\n\n    /*\n        Subject: 认证主体\n        Principals:  身份: 用户名\n        Credentials: 凭证: 密码\n\n        Realm: 域\n            1.jdbc realm | 2.jndi realm | 3.text realm\n\n     */\n}\n

从数据库中读取用户名密码 实现登录

\n

1.配置文件: jdbc_realm.ini (代码只需把读取的文件改成此文件即可测试使用)

\n
[main]\njdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm\ndataSource=com.mchange.v2.c3p0.ComboPooledDataSource\ndataSource.driverClass=com.mysql.jdbc.Driver\ndataSource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test\ndataSource.user=root\ndataSource.password=123456\njdbcRealm.dataSource=$dataSource\nsecurityManager.realms=$jdbcRealm\n

\n
\n

2.权限认证

核心要素:(资源,)权限,角色,用户

用户–(分配)–>角色–(拥有)–>权限–(控制)—->资源

\n
    \n
  • 用户代表访问系统的用户,即subject。
  • \n
\n
\n

三种授权方式

\n
\n
1. 编程式授权\n    1.1. 基于角色的访问控制\n    1.2. 基于权限的访问控制\n2. 注解式授权\n3.JSP标签授权\n

\n

####步骤1: 封装一个工具类

\n
public class ShiroUtils {\n    public static Subject login(String conf, String username, String passowrd) {\n        //初始化SecurityManager工厂 conf是配置文件名称\n        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:" + conf + ".ini");\n        //获取SecurityManager实例\n        SecurityManager manager = factory.getInstance();\n        //把SecurityManager实例绑定到SecurityUtils\n        SecurityUtils.setSecurityManager(manager);\n        //得到当前执行的用户\n        Subject subject = SecurityUtils.getSubject();\n        //创建token令牌, 用户/密码\n        UsernamePasswordToken token = new UsernamePasswordToken(username, passowrd);\n\n        try {\n            //身份认证 登录\n            subject.login(token);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        return subject;\n    }\n}\n

####步骤2: 测试多种访问控制

\n
/*\n    基于角色的访问控制方式1\n\n    配置文件:shiro_role.ini\n    ******************************\n    [users]\n    zj2626=123456,role1,role2\n    py2626=123456,role1\n    ay2626=456789,role3\n    ******************************\n     */\n    @Test\n    public void testHas() {\n        Subject sub = ShiroUtils.login("shiro_config/shiro_role", "zj2626", "123456");\n\n        //判断有没有权限 返回布尔 表示验证的成功与否\n        boolean bool = sub.hasRole("role1");\n        if (bool) {\n            System.out.println("HAS");\n        }\n\n        //判断有没有权限,一次多个分别判断 返回布尔数组\n        boolean[] booleans = sub.hasRoles(Arrays.asList("role1", "role2", "role3"));\n        int i = 0;\n        while (booleans[i]) {\n            i++;\n\n            if (booleans.length <= i) {\n                break;\n            }\n        }\n\n        //所有的角色都有才返回true\n        System.out.println(sub.hasAllRoles(Arrays.asList("role1", "role2", "role3")));\n\n        //判断有没有权限 没有则抛异常\n        sub.checkRole("role1");\n        sub.checkRole("role3");\n\n        //判断多个权限 有一个没有就抛异常 (2种参数形式)\n        sub.checkRoles(Arrays.asList("role1", "role2", "role3"));\n        sub.checkRoles("role1", "role2", "role3");\n\n        //退出登陆\n        sub.logout();\n    }\n\n    /*\n    基于权限的访问控制方式(过程同上)\n\n    配置文件:shiro_permision.ini\n    ******************************\n    [users]\n    zj2626=123456,role1,role2\n    py2626=123456,role1\n    ay2626=456789,role3\n    [roles]\n    role1=user:select\n    role2=user:add,user:update,user:delete\n    ******************************\n     */\n    @Test\n    public void testPermition() {\n        Subject sub = ShiroUtils.login("shiro_config/shiro_permision", "py2626", "123456");\n\n        System.out.println("用户是否有权限 user:select:" + sub.isPermitted("user:select"));    //true\n        System.out.println("用户是否有权限 user:update:" + sub.isPermitted("user:update"));    //false\n\n        boolean[] booleans = sub.isPermitted("user:add", "user:select");\n        System.out.println(booleans[0] + "____" + booleans[1]);\n\n        System.out.println(sub.isPermittedAll("user:add", "user:select"));\n\n        //没有会抛出异常\n        sub.checkPermission("user:select");\n        sub.checkPermissions("user:select", "user:update");\n\n        sub.logout();\n    }\n

\n
\n

3.集成web进行测试

1.新建一个maven的web项目

\"项目目录文件\"\n
\n

web.xml 配置shiro的必须的配置: 监听器,过滤器

\n
\n
<?xml version="1.0" encoding="UTF-8"?>\n<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"\n         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"\n         version="3.1">\n    <display-name>Archetype Created Web Application</display-name>\n\n    <!--三个servlet配置   /login跳转到登陆页面  /home跳转到主页,即登陆成功页面 /admin用来测试角色和权限-->\n    <servlet>\n        <servlet-name>loginServlet</servlet-name>\n        <servlet-class>com.servlet.LoginServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>loginServlet</servlet-name>\n        <url-pattern>/login</url-pattern>\n    </servlet-mapping>\n    <servlet>\n        <servlet-name>homeServlet</servlet-name>\n        <servlet-class>com.servlet.HomeServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>homeServlet</servlet-name>\n        <url-pattern>/home</url-pattern>\n    </servlet-mapping>\n    <servlet>\n        <servlet-name>adminServlet</servlet-name>\n        <servlet-class>com.servlet.AdminServlet</servlet-class>\n    </servlet>\n    <servlet-mapping>\n        <servlet-name>adminServlet</servlet-name>\n        <url-pattern>/admin</url-pattern>\n    </servlet-mapping>\n\n    <!--shiro监听-->\n    <listener>\n        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>\n    </listener>\n\n    <!--shiro过滤器 这里过滤所有的地址 并且指定权限配置文件(一般项目中权限的配置存放在数据库中)-->\n    <filter>\n        <filter-name>ShiroFilter</filter-name>\n        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>\n        <init-param>\n            <param-name>config</param-name>\n            <param-value>classpath:shiro.ini</param-value>\n        </init-param>\n    </filter>\n    <filter-mapping>\n        <filter-name>ShiroFilter</filter-name>\n        <url-pattern>/*</url-pattern>\n    </filter-mapping>\n\n</web-app>\n
\n

pom.xml :所需依赖

\n
\n
<dependencies>\n    <dependency>\n        <groupId>org.apache.shiro</groupId>\n        <artifactId>shiro-web</artifactId>\n        <version>1.3.2</version>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.shiro</groupId>\n        <artifactId>shiro-core</artifactId>\n        <version>1.3.2</version>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.shiro</groupId>\n        <artifactId>shiro-spring</artifactId>\n        <version>1.3.2</version>\n    </dependency>\n    <dependency>\n        <groupId>org.apache.tomcat</groupId>\n        <artifactId>tomcat-servlet-api</artifactId>\n        <version>8.5.4</version>\n        <scope>provided</scope>\n    </dependency>\n    <dependency>\n        <groupId>org.slf4j</groupId>\n        <artifactId>slf4j-log4j12</artifactId>\n        <version>1.7.7</version>\n        <scope>compile</scope>\n    </dependency>\n    <dependency>\n        <groupId>commons-logging</groupId>\n        <artifactId>commons-logging</artifactId>\n        <version>1.2</version>\n    </dependency>\n    <dependency>\n        <groupId>commons-beanutils</groupId>\n        <artifactId>commons-beanutils</artifactId>\n        <version>1.9.3</version>\n    </dependency>\n    <dependency>\n        <groupId>commons-collections</groupId>\n        <artifactId>commons-collections</artifactId>\n        <version>3.2.1</version>\n    </dependency>\n    <dependency>\n        <groupId>jstl</groupId>\n        <artifactId>jstl</artifactId>\n        <version>1.2</version>\n    </dependency>\n</dependencies>\n
\n

shiro.ini: 权限配置文件,配置什么用户有什么角色,什么角色有什么权限

\n
\n
[main]\nauthc.loginUrl=/login\nperms.unauthorizedUrl=/noAuth.jsp\nroles.unauthorizedUrl=/noAuth.jsp\n[users]\nzj2626=123456,admin\nay2626=456789,student\n[roles]\nadmin=user:*,student:select\nstudent:student:*\n[urls]\n/login=anon\n/home=authc\n/admin=roles[admin]\n
1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址…(loginUrl是authc的一个属性)
2.roles.unauthorizeUrl配置角色认证不通过跳转的地址…(noAuth.jsp页面目前只有一行字)
3.perms.unauthorizeUrl配置权限认证不通过跳转的地址
4.[users]下配置用户身份信息以及用户角色
5.[roles]下配置角色以及角色的控制权限
6.[urls]下配置访问地址所需的权限, 其中值为”anon过滤器”表示地址不需要登录即可访问; “authc过滤器”表示地址登录才能访问
7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围
8.值为 perms[“student:create”] 表示 必须有权限为”student:create”的用户才能范围
9.多个过滤器用”,”隔开 而且相互为”且”的关系(必须同时满足才能访问)
10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef…..)
11.地址可以使用表示匹配任意个任意字符(eg: /home=authc 表示可过滤 /home123; /homeef…..)
12.地址可以使用表示匹配多路径(eg: /home/=authc 表示可过滤 /home/abc; /home/aaa/bbb…..)

2.编写sevlet代码

\n

LoginServlet.java :身份验证地址

\n
\n
package com.servlet;\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.authc.UsernamePasswordToken;\nimport org.apache.shiro.subject.Subject;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Created by zj on 2017/4/10.\n */\npublic class LoginServlet extends HttpServlet {\n    /**\n     * 跳转登录界面\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("user no login");\n        resp.sendRedirect("login.jsp");\n    }\n\n    /**\n     * 进行登录\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("登录");\n        String userName = req.getParameter("userName");\n        String password = req.getParameter("password");\n\n        Subject subject = SecurityUtils.getSubject();\n        //创建token令牌, 用户/密码\n        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);\n        try {\n            //身份认证 登录\n            subject.login(token);\n            System.out.println("登录成功");\n            resp.sendRedirect("success.jsp");\n        } catch (Exception e) {\n            System.out.println("账号密码不对");\n            e.printStackTrace();\n            resp.sendRedirect("login.jsp");\n        }\n    }\n}\n\n\n//******************************************************************\nlogin.jsp\n\n<%@ page language="java" pageEncoding="UTF-8" %>\n<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>\n<!DOCTYPE HTML>\n<html>\n<body>\n<h2>Hello World</h2>\n登录:\n<form action="/login" method="post">\n    <input type="text" value="" name="userName">\n    <input type="password" value="" name="password">\n    <input type="submit" value="登录">\n</form>\n</body>\n</html>\n
\n

HomeServlet.java :登录成功以及退出登录地址

\n
\n
package com.servlet;\n\nimport org.apache.shiro.SecurityUtils;\nimport org.apache.shiro.subject.Subject;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Created by zj on 2017/4/10.\n */\npublic class HomeServlet extends HttpServlet {\n    /**\n     * 进入主页(登陆成功界面)\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("主页 get");\n\n        req.getRequestDispatcher("success.jsp").forward(req, resp);\n\n    }\n\n    /**\n     * 用来退出登陆\n     *\n     * @param req\n     * @param resp\n     * @throws ServletException\n     * @throws IOException\n     */\n    @Override\n    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n        System.out.println("主页 post");\n\n        System.out.println("login out");\n\n        //退出登录\n        Subject subject = SecurityUtils.getSubject();\n        subject.logout();\n\n        resp.sendRedirect("login.jsp");\n    }\n}\n\n//******************************************************************\nsuccess.jsp\n\n<%@ page contentType="text/html;charset=UTF-8" language="java" %>\n<html>\n<body>\n<h2>Hello World!</h2> 成功!!!\n<form action="/home" method="post">\n    <input type="submit" value="退出登陆">\n</form>\n</body>\n</html>\n
\n

AdminServlet.java

\n
\n
package com.servlet;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Created by zj on 2017/4/10.\n */\npublic class AdminServlet extends HttpServlet {\n    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n        System.out.println("ADMIN GET");\n    }\n\n    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n        System.out.println("ADMIN POST");\n    }\n}\n

3.启动项目

测试身份认证
\n

不登录情况下输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面

\n
\n\"图片说明\"\n
\n

输入正确的用户名(ay2626)密码 点击登录 成功; 再次输入地址 http://localhost:8080/home 跳转到成功的jsp

\n
\n\"图片说明\"\n
\n

点击退出登录 成功; 再次输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面

\n
\n
测试角色认证
\n

登录 :进行其他认证前先进行身份认证 使用ay2626用户登录(其角色只有student) 登录成功 跳转到success.jsp

\n
\n
\n

输入地址http://localhost:8080/admin 跳转到 http://localhost:8080/noAuth.jsp 表示没有权限访问此地址

\n
\n
\n

退出登录 用zj2626再次登录测试 登录成功 再次输入http://localhost:8080/admin 控制台打印”ADMIN POST” 表示访问成功

\n
\n\"图片说明\"\n
测试权限认证
\n

可把配置文件中 /admin的过滤器改为 /admin=perms[“student:create”] 进行测试

\n
\n
\n

测试发现 有权限的ay2626用户可以访问而没有权限的zj2626不能访问

\n
\n
\n
\n

自定义Realm

\n

实际开发中用户权限的配置要存放在数据库,so需要使用自定义realm来读取数据库中权限配置然后为用户赋予权限

\n
\n
测试开发步骤
\n

1.添加数据库依赖

\n
\n
<dependency>\n    <groupId>mysql</groupId>\n    <artifactId>mysql-connector-java</artifactId>\n    <version>5.1.39</version>\n    <scope>compile</scope>\n</dependency>\n
\n

2.设计构建测试的数据库表:有三个表:分别存储用户,角色,权限 并其他两个都与角色关联, 然后存入部分数据 ,再根据三个表建立对应实体(简单实体)

\n
\n\"图片说明\"\n
\n

3.添加数据库操作类(写的并不完善 仅测试使用)

\n
\n
package com.servlet;\n\nimport java.sql.*;\n\n/**\n * Created by zj on 2017/4/11.\n */\npublic class DBUtil {\n\n    //获取数据库连接\n    private static Connection getConnection() {\n        try {\n            Class.forName("com.mysql.jdbc.Driver");\n\n            Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "fangshuoit");\n\n            return connection;\n        } catch (ClassNotFoundException | SQLException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**\n     * 通过用户名获取用户信息\n     *\n     * @param name\n     * @return\n     * @throws SQLException\n     */\n    public static User getByUserName(String name) throws SQLException {\n        String sql = "select * from ay_user where loginName = ?";\n\n        PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n        preparedStatement.setString(1, name);\n\n        ResultSet resultSet = preparedStatement.executeQuery();\n        if (resultSet.next()) {\n            return new User(resultSet.getInt("id"), resultSet.getString("loginName"), resultSet.getString("password"));\n        }\n\n        return null;\n    }\n\n    /**\n     * 通过用户名获取用户角色\n     *\n     * @param name\n     * @return\n     * @throws SQLException\n     */\n    public static Role getRolesByUserName(String name) throws SQLException {\n        String sql = "select roleId from ay_user where loginName = ?";\n\n        PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n        preparedStatement.setString(1, name);\n\n        ResultSet resultSet = preparedStatement.executeQuery();\n        if (resultSet.next()) {\n            Integer id = resultSet.getInt("roleId");\n\n            sql = "select * from ay_role where id = ?";\n            preparedStatement = getConnection().prepareStatement(sql);\n            preparedStatement.setInt(1, id);\n            resultSet = preparedStatement.executeQuery();\n            if (resultSet.next()) {\n                return new Role(resultSet.getInt("id"), resultSet.getString("name"));\n            }\n        }\n\n        return null;\n    }\n\n    /**\n     * 通过角色id获取角色权限\n     *\n     * @param roleId\n     * @return\n     * @throws SQLException\n     */\n    public static Perms getPermsByRole(Integer roleId) throws SQLException {\n        String sql = "select * from ay_perms where roleId = ?";\n\n        PreparedStatement preparedStatement = getConnection().prepareStatement(sql);\n        preparedStatement.setInt(1, roleId);\n\n        ResultSet resultSet = preparedStatement.executeQuery();\n        if (resultSet.next()) {\n            return new Perms(resultSet.getInt("id"), resultSet.getString("name"), resultSet.getInt("roleId"));\n        }\n\n        return null;\n    }\n}\n
\n

3.编写自定义的Realm类 需要AuthorizingRealm类并实现两个方法; 第一个是用来身份验证,第二是用来角色权限验证

\n
\n
public class MyRealm extends AuthorizingRealm {\n\n    /**\n     * 验证当前登录的用户(身份认证), 不再需要在配置文件中配置用户的信息和其角色信息\n     * \n     * @param token 封装有用户的信息\n     * @return\n     * @throws AuthenticationException\n     */\n    @Override\n    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {\n        String userName = (String) token.getPrincipal();\n\n        System.out.println("要登录的用户 : " + userName);\n\n        try {\n            User user = DBUtil.getByUserName(userName);//这里只是通过用户名验证并获取用户信息,实际开发中需要用户名以及加密的密码\n            if (user != null) {\n                return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), "XX");//返回登录信息\n            } else\n                return null;\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    /**( 身份验证(登录)以后 )\n     * 为当前用户授予角色和权限(根据登录的用户名,读取数据库中其角色和权限)\n     *\n     * @param principals 封装了身份信息\n     * @return\n     */\n    @Override\n    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {\n        String userName = (String) principals.getPrimaryPrincipal();\n        System.out.println("要权限的用户 : " + userName);\n\n        SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();\n\n        try {\n            Role role = DBUtil.getRolesByUserName(userName);\n            if (null != role) {\n                Set<String> set = new HashSet<>();\n                System.out.println("获得的角色: "+ role.getName());\n                set.add(role.getName());\n                authenticationInfo.setRoles(set);//赋予角色\n\n                Perms perms = DBUtil.getPermsByRole(role.getId());\n                Set<String> set2 = new HashSet<>();\n                set2.add(perms.getName());\n                System.out.println("获得的权限: "+ perms.getName());\n                authenticationInfo.setStringPermissions(set2);//赋予权限\n\n                return authenticationInfo;\n            }\n        } catch (SQLException e) {\n\n        }\n\n        return null;\n    }\n}\n
\n

4.修改配置文件shiro.ini ,引入自定义Realms,并去掉原来的 [users]和[roles]下的配置

\n
\n
[main]\nauthc.loginUrl=/login\nperms.unauthorizedUrl=/noAuth.jsp\nroles.unauthorizedUrl=/noAuth.jsp\n\nmyRealm=com.servlet.MyRealm\nsecurityManager.realms=$myRealm\n[urls]\n/login=anon\n/home=authc\n/admin=roles[admin]\n
1.myRealm指向自定义Realm的位置
2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用”,”隔开
\n

5.启动项目测试 发现:使用zj2626登录可以访问 /admin地址;而ay2626登录不能访问(没有user角色);而且每次请求都会进行认证(控制台打印信息)

\n
"},{"title":"Mybatis 高级结果映射 ResultMap Association Collection","comments":1,"description":null,"date":"2017-06-18T16:00:00.000Z","_content":"转自 http://blog.csdn.net/ilovejava_2010/article/details/8180521\n\n# 高级结果映射\n> MyBatis的创建基于这样一个思想:数据库并不是您想怎样就怎样的。虽然我们希望所有的数据库遵守第三范式或BCNF(修正的第三范式),但它们不是。如果有一个数据库能够完美映射到所有应用程序,也将是非常棒的,但也没有。结果集映射就是MyBatis为解决这些问题而提供的解决方案。例如,我们如何映射下面这条语句?\n\n\n\n\n \n \n \n\n\n> 您可能想要把它映射到一个智能的对象模型,包括由一个作者写的一个博客,有许多文章(Post,帖子),每个文章由0个或者多个评论和标签。下面是一个复杂ResultMap 的完整例子(假定作者、博客、文章、评论和标签都是别名)。仔细看看这个例子,但是不用太担心,我们会一步步地来分析,一眼看上去可能让人沮丧,但是实际上非常简单的\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n这个resultMap 的元素的子元素比较多,讨论起来比较宽泛。下面我们从概念上概览一下这个resultMap的元素。\n \n* resultMap\n * constructor–实例化的时候通过构造器将结果集注入到类中\n * oidArg– ID 参数; 将结果集标记为ID,以方便全局调用\n * oarg–注入构造器的结果集\n * id–结果集ID,将结果集标记为ID,以方便全局调用\n * result–注入一个字段或者javabean属性的结果\n * association–复杂类型联合;许多查询结果合成这个类型\n * 嵌套结果映射– associations能引用自身,或者从其它地方引用\n * collection–复杂类型集合\n * 嵌套结果映射– collections能引用自身,或者从其它地方引用\n * discriminator–使用一个结果值以决定使用哪个resultMap\n * ocase–基于不同值的结果映射\n * §嵌套结果映射–case也能引用它自身, 所以也能包含这些同样的元素。它也可以从外部引用resultMap\n \n> 最佳实践:逐步地生成resultMap,单元测试对此非常有帮助。如果您尝试一下子就生成像上面这样巨大的resultMap,可能会出错,并且工作起来非常吃力。从简单地开始,再一步步地扩展,并且进行单元测试。使用框架开发有一个缺点,它们有时像是一个黑合。为了确保达到您所预想的行为,最好的方式就是进行单元测试。这对提交bugs 也非常有用。\n \n下一节,我们一步步地查看这些细节。\n\n---\n\n### id, result元素\n \n \n \n>这是最基本的结果集映射。id 和result 将列映射到属性或简单的数据类型字段(String, int, double, Date等)。\n这两者唯一不同的是,在比较对象实例时id 作为结果集的标识属性。这有助于提高总体性能,特别是应用缓存和嵌套结果映射的时候。\n \nId、result属性如下:\n\n| Attribute | Description |\n| ------------- |:-------------:|\n| property | 映射数据库列的字段或属性。如果JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。
否则,MyBatis 将搜索给定名称的字段。两种情况下您都可以使用逗点的属性形式。比如,您可以映射到“username”,也可以映射到“address.street.number”。 | \n| column | 数据库的列名或者列标签别名。与传递给resultSet.getString(columnName)的参数名称相同。 | \n| javaType | 完整Java类名或别名(参考上面的内置别名列表)。
如果映射到一个JavaBean,那MyBatis 通常会自行检测到。然而,如果映射到一个HashMap,
那您应该明确指定javaType 来确保所需行为 | \n| jdbcType |这张表下面支持的JDBC类型列表列出的JDBC类型。
这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。
如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。| \n| typeHandler | 我们已经在文档中讨论过默认类型处理器。
使用这个属性可以重写默认类型处理器。它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 | \n\n\n> 支持的JDBC类型 MyBatis支持如下的JDBC类型:\n\n{% qnimg blog_mybatis.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n> Constructor元素\n \n \n \n \n \n \n当属性与DTO,或者与您自己的域模型一起工作的时候,许多场合要用到不变类。通常,包含引用,或者查找的数据很少或者数据不会改变的的表,适合映射到不变类中。构造器注入允许您在类实例化后给类设值,这不需要通过public方法。MyBatis同样也支持private属性和JavaBeans的私有属性达到这一点,但是一些用户可能更喜欢使用构造器注入。构造器元素可以做到这点。\n\n\n 考虑下面的构造器:\n \n public class User {\n //…\n public User(int id, String username) {\n //…\n }\n //…\n }\n\n\n\n为了将结果注入构造器,MyBatis需要使用它的参数类型来标记构造器。Java没有办法通过参数名称来反射获得。因此当创建constructor 元素,确保参数是按顺序的并且指定了正确的类型。\n \n \n \n \n \n \n其它的属性与规则与id、result元素的一样。\n\n\n| Attribute | Description |\n| ------------- |:-------------:|\n| column | 数据库的列名或者列标签别名。
与传递给resultSet.getString(columnName)的参数名称相同。 | \n| javaType | 完整Java类名或别名(参考上面的内置别名列表)。
如果映射到一个JavaBean,那MyBatis 通常会自行检测到。
然而,如果映射到一个HashMap,那您应该明确指定javaType 来确保所需行为 | \n| jdbcType | 这张表下面支持的JDBC类型列表列出的JDBC类型。
这个属性只在insert,update 或delete 的时候针对允许空的列有用。
JDBC 需要这项,但MyBatis 不需要。
如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。| \n| typeHandler | 我们已经在文档中讨论过默认类型处理器。
使用这个属性可以重写默认类型处理器。它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 | \n\n\n> Association元素\n\n \n \n \n \n\nAssociation元素处理“has-one”(一对一)这种类型关系。比如在我们的例子中,一个Blog有一个Author。联合映射与其它的结果集映射工作方式差不多,指定property、column、javaType(通常MyBatis会自动识别)、jdbcType(如果需要)、typeHandler。\n\n不同的地方是您需要告诉MyBatis 如何加载一个联合查询。MyBatis使用两种方式来加载:\n\n* Nested Select:通过执行另一个返回预期复杂类型的映射SQL语句(即引用外部定义好的SQL语句块)。\n* Nested Results:通过嵌套结果映射(nested result mappings)来处理联接结果集(joined results)的重复子集。\n\n首先,让我们检查一下元素属性。正如您看到的,它不同于普通只有select和resultMap属性的结果映射。\n\n\n| Attribute | Description |\n| ------------- |:-------------:|\n| property | 映射数据库列的字段或属性。如果JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。
否则,MyBatis 将搜索给定名称的字段。两种情况下您都可以使用逗点的属性形式。
比如,您可以映射到“username”,也可以映射到“address.street.number”。 | \n| column | 数据库的列名或者列标签别名。
与传递给resultSet.getString(columnName)的参数名称相同。 | \n| javaType | 完整Java类名或别名(参考上面的内置别名列表)。如果映射到一个JavaBean,那MyBatis 通常会自行检测到。
然而,如果映射到一个HashMap,那您应该明确指定javaType 来确保所需行为 | \n| jdbcType |这张表下面支持的JDBC类型列表列出的JDBC类型。这个属性只在insert,update 或delete 的时候针对允许空的列有用。
JDBC 需要这项,但MyBatis 不需要。
如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。| \n| typeHandler | 我们已经在文档中讨论过默认类型处理器。使用这个属性可以重写默认类型处理器。
它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 | \n| select 联合嵌套选择(Nested Select for Association) | \t通过这个属性,通过ID引用另一个加载复杂类型的映射语句。
从指定列属性中返回的值,将作为参数设置给目标select 语句。表格下方将有一个例子。
注意:在处理组合键时,您可以使用column=”{prop1=col1,prop2=col2}”这样的语法,
设置多个列名传入到嵌套语句。这就会把prop1和prop2设置到目标嵌套语句的参数对象中。 | \n\n\n 例如: \n \n\n \n \n \n \n \n \n \n \n\n\n---\n\n我们使用两个select语句:一个用来加载Blog,另一个用来加载Author。Blog的resultMap 描述了使用“selectAuthor”语句来加载author的属性。\n如果列名和属性名称相匹配的话,所有匹配的属性都会自动加载。\n\n\n 译者注:\n 上面的例子,首先执行查出的数据都会自动赋值给”blogResult”的与列名匹配的属性,这时blog_id,title等就被赋值了。同时“blogResult”还有一个关联属性\"Author\",执行嵌套查询select=”selectAuthor”后,Author对象的属性id,username,password,email,bio也被赋于数据库匹配的值。\n \n Blog\n {\n blog_id;\n title;\n Author author\n {\n id;\n username;\n password;\n email;\n bio;\n \n }\n \n }\n\n\n\n\n> 虽然这个方法简单,但是对于大数据集或列表查询,就不尽如人意了。这个问题被称为“N+1 选择问题”(N+1 Selects Problem)。概括地说,N+1选择问题是这样产生的:\n\n* 您执行单条SQL语句去获取一个列表的记录( “+1”)。\n* 对列表中的每一条记录,再执行一个联合select 语句来加载每条记录更加详细的信息(“N”)。\n这个问题会导致成千上万的SQL语句的执行,因此并非总是可取的。\n\n上面的例子,MyBatis可以使用延迟加载这些查询,因此这些查询立马可节省开销。然而,如果您加载一个列表后立即迭代访问嵌套的数据,这将会调用所有的延迟加载,因此性能会变得非常糟糕。\n鉴于此,这有另外一种方式。\n联合嵌套结果集(Nested Results for Association)\n\n> resultMap \t\n一个可以映射联合嵌套结果集到一个适合的对象视图上的ResultMap 。这是一个替代的方式去调用另一个select 语句。它允许您去联合多个表到一个结果集里。这样的结果集可能包括冗余的、重复的需要分解和正确映射到一个嵌套对象视图的数据组。简言之,MyBatis 让您把结果映射‘链接’到一起,用来处理嵌套结果。举个例子会更好理解,例子在表格下方。\n\n\n您已经在上面看到了一个非常复杂的嵌套联合的例子,接下的演示的例子会更简单一些。我们把Blog和Author表联接起来查询,而不是执行分开的查询语句:\n\n\n \n \n 注意到这个连接(join),要确保所有的别名都是唯一且无歧义的。这使映射容易多了,现在我们来映射结果集:\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n在上面的例子中,您会看到Blog的作者(“author”)联合一个“authorResult”结果映射来加载Author实例。\n**重点提示** :id元素在嵌套结果映射中扮演了非常重要的角色,您应该总是指定一个或多个属性来唯一标识这个结果集。事实上,如果您没有那样做,MyBatis也会工作,但是会导致严重性能开销。选择尽量少的属性来唯一标识结果,而使用主键是最明显的选择(即使是复合主键)。\n上面的例子使用一个扩展的resultMap 元素来联合映射。这可使Author结果映射可重复使用。然后,如果您不需要重用它,您可以直接嵌套这个联合结果映射。下面例子就是使用这样的方式: \n\n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n在上面的例子中您已经看到如果处理“一对一”(“has one”)类型的联合查询。但是对于“一对多”(“has many”)的情况如果处理呢?这个问题在下一节讨论。\n\n### Collection元素 \n\n\n \n \n \n \n \n \n\n\n\n> collection元素的作用差不多和association元素的作用一样。事实上,它们非常相似,以至于再对相似点进行描述会显得冗余,因此我们只关注它们的不同点。\n\n继续我们上面的例子,一个Blog只有一个Author。但一个Blog有许多帖子(文章)。在Blog类中,会像下面这样定义相应属性: \n\n private List posts;\n\n> 映射一个嵌套结果集到一个列表,我们使用collection元素。就像association 元素那样,我们使用嵌套查询,或者从连接中嵌套结果集。 \n集合嵌套选择(Nested Select for Collection)\n\n首先我们使用嵌套选择来加载Blog的文章。 \n\n\n \n \n \n \n \n \n \n\n\n\n一看上去这有许多东西需要注意,但大部分看起与我们在association元素中学过的相似。首先,您会注意到我们使用了collection元素,然后会注意到一个新的属性“ofType”。这个元素是用来区别JavaBean属性(或者字段)类型和集合所包括的类型。因此您会读到下面这段代码。\n \n \n\n* 理解为:“一个名为posts,类型为Post的ArrayList集合(A collection of posts in an ArrayList of type Post)” 。\n\njavaType属性不是必须的,通常MyBatis 会自动识别,所以您通常可以简略地写成:\n\n \n\n\n#### 集合的嵌套结果集(Nested Results for Collection)\n\n这时候,您可能已经猜出嵌套结果集是怎样工作的了,因为它与association非常相似,只不过多了一个属性“ofType”。\n\n让我们看下这个SQL: \n\n\n \n \n\n\n同样,我们把Blog和Post两张表连接在一起,并且也保证列标签名在映射的时候是唯一且无歧义的。现在将Blog和Post的集合映射在一起是多么简单:\n\n\n \n \n \n \n \n \n \n \n \n\n\n再次强调一下,id 元素是非常重要的。如果您忘了或者不知道id 元素的作用,请先读一下上面association 一节。\n\n如果希望结果映射有更好的可重用性,您可以使用下面的方式:\n\n\n \n \n \n \n \n \n \n \n \n \n \n\n\n* Note:在您的映射中没有深度、宽度、联合和集合数目的限制。但应该谨记,在进行映射的时候也要考虑性能的因素。应用程序的单元测试和性能测试帮助您发现最好的方式可能要花很长时间。但幸运的是,MyBatis允许您以后可以修改您的想法,这时只需要修改少量代码就行了。\n\n关于高级联合和集合映射是一个比较深入的课题,文档只能帮您了解到这里,多做一些实践,一切将很快变得容易理解。\n\n### Discriminator元素\n\n\n \n \n \n \n\n> 有时候一条数据库查询可能会返回包括各种不同的数据类型的结果集。Discriminator(识别器)元素被设计来处理这种情况,以及其它像类继承层次情况。识别器非常好理解,它就像java里的switch语句。\n \nDiscriminator定义要指定column和javaType属性。列是MyBatis将要取出进行比较的值,javaType用来确定适当的测试是否正确运行(虽然String在大部分情况下都可以工作),例: \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n在这个例子中,MyBatis将会从结果集中取出每条记录,然后比较它的vehicle type的值。如果匹配任何discriminator中的case,它将使用由case指定的resultMap。这是排它性的,换句话说,其它的case的resultMap将会被忽略(除非使用我们下面说到的extended)。如果没有匹配到任何case,MyBatis只是简单的使用定义在discriminator块外面的resultMap。所以,如果carResult像下面这样定义:\n\n\n \n \n \n\n\n那么,只有doorCount属性会被加载。这样做是为了与识别器cases群组完全独立开来,哪怕它与上一层的resultMap 一点关系都没有。在刚才的例子里我们当然知道cars和vehicles的关系,a Car is-a Vehicle。因此,我们也要把其它属性加载进来。我们要稍稍改动一下resultMap:\n\n\n \n \n \n\n \n现在,vehicleResult和carResult的所有属性都会被加载。\n可能有人会认为这样扩展映射定义有一点单调了,所以还有一种可选的更加简单明了的映射风格语法。例如:\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n* 记住:对于这么多的结果映射,如果您不指定任何的结果集,那么MyBatis 会自动地将列名与属性相匹配。所以上面所举的例子比实际中需要的要详细。尽管如此,大部分数据库有点复杂,并且它并不是所有情况都是完全可以适用的。\n\n#### Cache元素\n> MyBatis包含一个强大的、可配置、可定制的查询缓存机制。MyBatis 3 的缓存实现有了许多改进,使它更强大更容易配置。默认的情况,缓存是没有开启,除了会话缓存以外,它可以提高性能,且能解决循环依赖。开启二级缓存,您只需要在SQL映射文件中加入简单的一行:\n \n \n \n\n这句简单的语句作用如下:\n\n* 所有映射文件里的select语句的结果都会被缓存。\n\n* 所有映射文件里的insert、update和delete语句执行都会清空缓存。\n\n* 缓存使用最近最少使用算法(LRU)来回收。\n\n* 缓存不会被设定的时间所清空。\n\n* 每个缓存可以存储1024 个列表或对象的引用(不管查询方法返回的是什么)。\n\n* 缓存将作为“读/写”缓存,意味着检索的对象不是共享的且可以被调用者安全地修改,而不会被其它调用者或者线程干扰。\n\n所有这些特性都可以通过cache元素进行修改。例如:\n\n \n \n\n这种高级的配置创建一个每60秒刷新一次的FIFO 缓存,存储512个结果对象或列表的引用,并且返回的对象是只读的。因此在不用的线程里的调用者修改它们可能会引用冲突。\n \n\n可用的回收算法如下:\n\n* LRU–最近最少使用:移出最近最长时间内都没有被使用的对象。\n\n* FIFO–先进先出:移除最先进入缓存的对象。\n\n* SOFT–软引用: 基于垃圾回收机制和软引用规则来移除对象(空间内存不足时才进行回收)。\n\n* WEAK–弱引用:基于垃圾回收机制和弱引用规则(垃圾回收器扫描到时即进行回收)。\n\n默认使用LRU。\n\n> flushInterval:设置任何正整数,代表一个以毫秒为单位的合理时间。默认是没有设置,因此没有刷新间隔时间被使用,在语句每次调用时才进行刷新。\n\n> Size:属性可以设置为一个正整数,您需要留意您要缓存对象的大小和环境中可用的内存空间。默认是1024。\n\n> readOnly:属性可以被设置为true 或false。只读缓存将对所有调用者返回同一个实例。因此这些对象都不能被修改,这可以极大的提高性能。可写的缓存将通过序列化来返回一个缓存对象的拷贝。这会比较慢,但是比较安全。所以默认值是false。\n \n\n使用自定义缓存\n\n除了上面已经定义好的缓存方式,您能够通过您自己的缓存实现来完全重写缓存行为,或者通过创建第三方缓存解决方案的适配器。\n\n \n \n这个例子演示了如果自定义缓存实现。由type指定的类必须实现org.mybatis.cache.Cache接口。这个接口是MyBatis框架比较复杂的接口之一,先给个示例:\n\n public interface Cache {\n String getId();\n int getSize();\n void putObject(Object key, Object value);\n Object getObject(Object key);\n boolean hasKey(Object key);\n Object removeObject(Object key);\n void clear();\n ReadWriteLock getReadWriteLock();\n }\n \n\n要配置您的缓存,简单地添加一个公共的JavaBeans 属性到您的缓存实现中,然后通过cache 元素设置属性进行传递,下面示例,将在您的缓存实现上调用一个setCacheFile(String file)方法。\n \n \n \n \n \n\n您可以使用所有简单的JavaBeans属性,MyBatis会自动进行转换。\n\n需要牢记的是一个缓存配置和缓存实例都绑定到一个SQL Map 文件命名空间。因此,所有的这个相同命名空间的语句也都和这个缓存绑定。语句可以修改如何与这个缓存相匹配,或者使用两个简单的属性来完全排除它们自己。默认情况下,语句像下面这样来配置:\n\n \n select \n B.id as blog_id, \n B.title as blog_title, \n B.author_id as blog_author_id, \n A.id as author_id, \n A.username as author_username, \n A.password as author_password, \n A.email as author_email, \n A.bio as author_bio, \n A.favourite_section as author_favourite_section, \n P.id as post_id, \n P.blog_id as post_blog_id, \n P.author_id as post_author_id, \n P.created_on as post_created_on, \n P.section as post_section, \n P.subject as post_subject, \n P.draft as draft, \n P.body as post_body, \n C.id as comment_id, \n C.post_id as comment_post_id, \n C.name as comment_name, \n C.comment as comment_text, \n T.id as tag_id, \n T.name as tag_name \n from Blog B \n left outer join Author A on B.author_id = A.id \n left outer join Post P on B.id = P.blog_id \n left outer join Comment C on P.id = C.post_id \n left outer join Post_Tag PT on PT.post_id = P.id \n left outer join Tag T on PT.tag_id = T.id \n where B.id = #{id} \n \n \n\n\n> 您可能想要把它映射到一个智能的对象模型,包括由一个作者写的一个博客,有许多文章(Post,帖子),每个文章由0个或者多个评论和标签。下面是一个复杂ResultMap 的完整例子(假定作者、博客、文章、评论和标签都是别名)。仔细看看这个例子,但是不用太担心,我们会一步步地来分析,一眼看上去可能让人沮丧,但是实际上非常简单的\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n这个resultMap 的元素的子元素比较多,讨论起来比较宽泛。下面我们从概念上概览一下这个resultMap的元素。\n \n* resultMap\n * constructor–实例化的时候通过构造器将结果集注入到类中\n * oidArg– ID 参数; 将结果集标记为ID,以方便全局调用\n * oarg–注入构造器的结果集\n * id–结果集ID,将结果集标记为ID,以方便全局调用\n * result–注入一个字段或者javabean属性的结果\n * association–复杂类型联合;许多查询结果合成这个类型\n * 嵌套结果映射– associations能引用自身,或者从其它地方引用\n * collection–复杂类型集合\n * 嵌套结果映射– collections能引用自身,或者从其它地方引用\n * discriminator–使用一个结果值以决定使用哪个resultMap\n * ocase–基于不同值的结果映射\n * §嵌套结果映射–case也能引用它自身, 所以也能包含这些同样的元素。它也可以从外部引用resultMap\n \n> 最佳实践:逐步地生成resultMap,单元测试对此非常有帮助。如果您尝试一下子就生成像上面这样巨大的resultMap,可能会出错,并且工作起来非常吃力。从简单地开始,再一步步地扩展,并且进行单元测试。使用框架开发有一个缺点,它们有时像是一个黑合。为了确保达到您所预想的行为,最好的方式就是进行单元测试。这对提交bugs 也非常有用。\n \n下一节,我们一步步地查看这些细节。\n\n---\n\n### id, result元素\n \n \n \n>这是最基本的结果集映射。id 和result 将列映射到属性或简单的数据类型字段(String, int, double, Date等)。\n这两者唯一不同的是,在比较对象实例时id 作为结果集的标识属性。这有助于提高总体性能,特别是应用缓存和嵌套结果映射的时候。\n \nId、result属性如下:\n\n| Attribute | Description |\n| ------------- |:-------------:|\n| property | 映射数据库列的字段或属性。如果JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。
否则,MyBatis 将搜索给定名称的字段。两种情况下您都可以使用逗点的属性形式。比如,您可以映射到“username”,也可以映射到“address.street.number”。 | \n| column | 数据库的列名或者列标签别名。与传递给resultSet.getString(columnName)的参数名称相同。 | \n| javaType | 完整Java类名或别名(参考上面的内置别名列表)。
如果映射到一个JavaBean,那MyBatis 通常会自行检测到。然而,如果映射到一个HashMap,
那您应该明确指定javaType 来确保所需行为 | \n| jdbcType |这张表下面支持的JDBC类型列表列出的JDBC类型。
这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。
如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。| \n| typeHandler | 我们已经在文档中讨论过默认类型处理器。
使用这个属性可以重写默认类型处理器。它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 | \n\n\n> 支持的JDBC类型 MyBatis支持如下的JDBC类型:\n\n{% qnimg blog_mybatis.png title:图片标题 alt:图片说明 extend:?imageView2/2/w/600 %}\n\n\n> Constructor元素\n \n \n \n \n \n \n当属性与DTO,或者与您自己的域模型一起工作的时候,许多场合要用到不变类。通常,包含引用,或者查找的数据很少或者数据不会改变的的表,适合映射到不变类中。构造器注入允许您在类实例化后给类设值,这不需要通过public方法。MyBatis同样也支持private属性和JavaBeans的私有属性达到这一点,但是一些用户可能更喜欢使用构造器注入。构造器元素可以做到这点。\n\n\n 考虑下面的构造器:\n \n public class User {\n //…\n public User(int id, String username) {\n //…\n }\n //…\n }\n\n\n\n为了将结果注入构造器,MyBatis需要使用它的参数类型来标记构造器。Java没有办法通过参数名称来反射获得。因此当创建constructor 元素,确保参数是按顺序的并且指定了正确的类型。\n \n \n \n \n \n \n其它的属性与规则与id、result元素的一样。\n\n\n| Attribute | Description |\n| ------------- |:-------------:|\n| column | 数据库的列名或者列标签别名。
与传递给resultSet.getString(columnName)的参数名称相同。 | \n| javaType | 完整Java类名或别名(参考上面的内置别名列表)。
如果映射到一个JavaBean,那MyBatis 通常会自行检测到。
然而,如果映射到一个HashMap,那您应该明确指定javaType 来确保所需行为 | \n| jdbcType | 这张表下面支持的JDBC类型列表列出的JDBC类型。
这个属性只在insert,update 或delete 的时候针对允许空的列有用。
JDBC 需要这项,但MyBatis 不需要。
如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。| \n| typeHandler | 我们已经在文档中讨论过默认类型处理器。
使用这个属性可以重写默认类型处理器。它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 | \n\n\n> Association元素\n\n \n \n \n \n\nAssociation元素处理“has-one”(一对一)这种类型关系。比如在我们的例子中,一个Blog有一个Author。联合映射与其它的结果集映射工作方式差不多,指定property、column、javaType(通常MyBatis会自动识别)、jdbcType(如果需要)、typeHandler。\n\n不同的地方是您需要告诉MyBatis 如何加载一个联合查询。MyBatis使用两种方式来加载:\n\n* Nested Select:通过执行另一个返回预期复杂类型的映射SQL语句(即引用外部定义好的SQL语句块)。\n* Nested Results:通过嵌套结果映射(nested result mappings)来处理联接结果集(joined results)的重复子集。\n\n首先,让我们检查一下元素属性。正如您看到的,它不同于普通只有select和resultMap属性的结果映射。\n\n\n| Attribute | Description |\n| ------------- |:-------------:|\n| property | 映射数据库列的字段或属性。如果JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。
否则,MyBatis 将搜索给定名称的字段。两种情况下您都可以使用逗点的属性形式。
比如,您可以映射到“username”,也可以映射到“address.street.number”。 | \n| column | 数据库的列名或者列标签别名。
与传递给resultSet.getString(columnName)的参数名称相同。 | \n| javaType | 完整Java类名或别名(参考上面的内置别名列表)。如果映射到一个JavaBean,那MyBatis 通常会自行检测到。
然而,如果映射到一个HashMap,那您应该明确指定javaType 来确保所需行为 | \n| jdbcType |这张表下面支持的JDBC类型列表列出的JDBC类型。这个属性只在insert,update 或delete 的时候针对允许空的列有用。
JDBC 需要这项,但MyBatis 不需要。
如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。| \n| typeHandler | 我们已经在文档中讨论过默认类型处理器。使用这个属性可以重写默认类型处理器。
它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 | \n| select 联合嵌套选择(Nested Select for Association) | \t通过这个属性,通过ID引用另一个加载复杂类型的映射语句。
从指定列属性中返回的值,将作为参数设置给目标select 语句。表格下方将有一个例子。
注意:在处理组合键时,您可以使用column=”{prop1=col1,prop2=col2}”这样的语法,
设置多个列名传入到嵌套语句。这就会把prop1和prop2设置到目标嵌套语句的参数对象中。 | \n\n\n 例如: \n \n\n \n \n \n \n \n \n \n \n\n\n---\n\n我们使用两个select语句:一个用来加载Blog,另一个用来加载Author。Blog的resultMap 描述了使用“selectAuthor”语句来加载author的属性。\n如果列名和属性名称相匹配的话,所有匹配的属性都会自动加载。\n\n\n 译者注:\n 上面的例子,首先执行查出的数据都会自动赋值给”blogResult”的与列名匹配的属性,这时blog_id,title等就被赋值了。同时“blogResult”还有一个关联属性\"Author\",执行嵌套查询select=”selectAuthor”后,Author对象的属性id,username,password,email,bio也被赋于数据库匹配的值。\n \n Blog\n {\n blog_id;\n title;\n Author author\n {\n id;\n username;\n password;\n email;\n bio;\n \n }\n \n }\n\n\n\n\n> 虽然这个方法简单,但是对于大数据集或列表查询,就不尽如人意了。这个问题被称为“N+1 选择问题”(N+1 Selects Problem)。概括地说,N+1选择问题是这样产生的:\n\n* 您执行单条SQL语句去获取一个列表的记录( “+1”)。\n* 对列表中的每一条记录,再执行一个联合select 语句来加载每条记录更加详细的信息(“N”)。\n这个问题会导致成千上万的SQL语句的执行,因此并非总是可取的。\n\n上面的例子,MyBatis可以使用延迟加载这些查询,因此这些查询立马可节省开销。然而,如果您加载一个列表后立即迭代访问嵌套的数据,这将会调用所有的延迟加载,因此性能会变得非常糟糕。\n鉴于此,这有另外一种方式。\n联合嵌套结果集(Nested Results for Association)\n\n> resultMap \t\n一个可以映射联合嵌套结果集到一个适合的对象视图上的ResultMap 。这是一个替代的方式去调用另一个select 语句。它允许您去联合多个表到一个结果集里。这样的结果集可能包括冗余的、重复的需要分解和正确映射到一个嵌套对象视图的数据组。简言之,MyBatis 让您把结果映射‘链接’到一起,用来处理嵌套结果。举个例子会更好理解,例子在表格下方。\n\n\n您已经在上面看到了一个非常复杂的嵌套联合的例子,接下的演示的例子会更简单一些。我们把Blog和Author表联接起来查询,而不是执行分开的查询语句:\n\n\n \n \n 注意到这个连接(join),要确保所有的别名都是唯一且无歧义的。这使映射容易多了,现在我们来映射结果集:\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n在上面的例子中,您会看到Blog的作者(“author”)联合一个“authorResult”结果映射来加载Author实例。\n**重点提示** :id元素在嵌套结果映射中扮演了非常重要的角色,您应该总是指定一个或多个属性来唯一标识这个结果集。事实上,如果您没有那样做,MyBatis也会工作,但是会导致严重性能开销。选择尽量少的属性来唯一标识结果,而使用主键是最明显的选择(即使是复合主键)。\n上面的例子使用一个扩展的resultMap 元素来联合映射。这可使Author结果映射可重复使用。然后,如果您不需要重用它,您可以直接嵌套这个联合结果映射。下面例子就是使用这样的方式: \n\n\n \n \n \n \n \n \n \n \n \n \n \n\n\n\n在上面的例子中您已经看到如果处理“一对一”(“has one”)类型的联合查询。但是对于“一对多”(“has many”)的情况如果处理呢?这个问题在下一节讨论。\n\n### Collection元素 \n\n\n \n \n \n \n \n \n\n\n\n> collection元素的作用差不多和association元素的作用一样。事实上,它们非常相似,以至于再对相似点进行描述会显得冗余,因此我们只关注它们的不同点。\n\n继续我们上面的例子,一个Blog只有一个Author。但一个Blog有许多帖子(文章)。在Blog类中,会像下面这样定义相应属性: \n\n private List posts;\n\n> 映射一个嵌套结果集到一个列表,我们使用collection元素。就像association 元素那样,我们使用嵌套查询,或者从连接中嵌套结果集。 \n集合嵌套选择(Nested Select for Collection)\n\n首先我们使用嵌套选择来加载Blog的文章。 \n\n\n \n \n \n \n \n \n \n\n\n\n一看上去这有许多东西需要注意,但大部分看起与我们在association元素中学过的相似。首先,您会注意到我们使用了collection元素,然后会注意到一个新的属性“ofType”。这个元素是用来区别JavaBean属性(或者字段)类型和集合所包括的类型。因此您会读到下面这段代码。\n \n \n\n* 理解为:“一个名为posts,类型为Post的ArrayList集合(A collection of posts in an ArrayList of type Post)” 。\n\njavaType属性不是必须的,通常MyBatis 会自动识别,所以您通常可以简略地写成:\n\n \n\n\n#### 集合的嵌套结果集(Nested Results for Collection)\n\n这时候,您可能已经猜出嵌套结果集是怎样工作的了,因为它与association非常相似,只不过多了一个属性“ofType”。\n\n让我们看下这个SQL: \n\n\n \n \n\n\n同样,我们把Blog和Post两张表连接在一起,并且也保证列标签名在映射的时候是唯一且无歧义的。现在将Blog和Post的集合映射在一起是多么简单:\n\n\n \n \n \n \n \n \n \n \n \n\n\n再次强调一下,id 元素是非常重要的。如果您忘了或者不知道id 元素的作用,请先读一下上面association 一节。\n\n如果希望结果映射有更好的可重用性,您可以使用下面的方式:\n\n\n \n \n \n \n \n \n \n \n \n \n \n\n\n* Note:在您的映射中没有深度、宽度、联合和集合数目的限制。但应该谨记,在进行映射的时候也要考虑性能的因素。应用程序的单元测试和性能测试帮助您发现最好的方式可能要花很长时间。但幸运的是,MyBatis允许您以后可以修改您的想法,这时只需要修改少量代码就行了。\n\n关于高级联合和集合映射是一个比较深入的课题,文档只能帮您了解到这里,多做一些实践,一切将很快变得容易理解。\n\n### Discriminator元素\n\n\n \n \n \n \n\n> 有时候一条数据库查询可能会返回包括各种不同的数据类型的结果集。Discriminator(识别器)元素被设计来处理这种情况,以及其它像类继承层次情况。识别器非常好理解,它就像java里的switch语句。\n \nDiscriminator定义要指定column和javaType属性。列是MyBatis将要取出进行比较的值,javaType用来确定适当的测试是否正确运行(虽然String在大部分情况下都可以工作),例: \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n在这个例子中,MyBatis将会从结果集中取出每条记录,然后比较它的vehicle type的值。如果匹配任何discriminator中的case,它将使用由case指定的resultMap。这是排它性的,换句话说,其它的case的resultMap将会被忽略(除非使用我们下面说到的extended)。如果没有匹配到任何case,MyBatis只是简单的使用定义在discriminator块外面的resultMap。所以,如果carResult像下面这样定义:\n\n\n \n \n \n\n\n那么,只有doorCount属性会被加载。这样做是为了与识别器cases群组完全独立开来,哪怕它与上一层的resultMap 一点关系都没有。在刚才的例子里我们当然知道cars和vehicles的关系,a Car is-a Vehicle。因此,我们也要把其它属性加载进来。我们要稍稍改动一下resultMap:\n\n\n \n \n \n\n \n现在,vehicleResult和carResult的所有属性都会被加载。\n可能有人会认为这样扩展映射定义有一点单调了,所以还有一种可选的更加简单明了的映射风格语法。例如:\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n* 记住:对于这么多的结果映射,如果您不指定任何的结果集,那么MyBatis 会自动地将列名与属性相匹配。所以上面所举的例子比实际中需要的要详细。尽管如此,大部分数据库有点复杂,并且它并不是所有情况都是完全可以适用的。\n\n#### Cache元素\n> MyBatis包含一个强大的、可配置、可定制的查询缓存机制。MyBatis 3 的缓存实现有了许多改进,使它更强大更容易配置。默认的情况,缓存是没有开启,除了会话缓存以外,它可以提高性能,且能解决循环依赖。开启二级缓存,您只需要在SQL映射文件中加入简单的一行:\n \n \n \n\n这句简单的语句作用如下:\n\n* 所有映射文件里的select语句的结果都会被缓存。\n\n* 所有映射文件里的insert、update和delete语句执行都会清空缓存。\n\n* 缓存使用最近最少使用算法(LRU)来回收。\n\n* 缓存不会被设定的时间所清空。\n\n* 每个缓存可以存储1024 个列表或对象的引用(不管查询方法返回的是什么)。\n\n* 缓存将作为“读/写”缓存,意味着检索的对象不是共享的且可以被调用者安全地修改,而不会被其它调用者或者线程干扰。\n\n所有这些特性都可以通过cache元素进行修改。例如:\n\n \n \n\n这种高级的配置创建一个每60秒刷新一次的FIFO 缓存,存储512个结果对象或列表的引用,并且返回的对象是只读的。因此在不用的线程里的调用者修改它们可能会引用冲突。\n \n\n可用的回收算法如下:\n\n* LRU–最近最少使用:移出最近最长时间内都没有被使用的对象。\n\n* FIFO–先进先出:移除最先进入缓存的对象。\n\n* SOFT–软引用: 基于垃圾回收机制和软引用规则来移除对象(空间内存不足时才进行回收)。\n\n* WEAK–弱引用:基于垃圾回收机制和弱引用规则(垃圾回收器扫描到时即进行回收)。\n\n默认使用LRU。\n\n> flushInterval:设置任何正整数,代表一个以毫秒为单位的合理时间。默认是没有设置,因此没有刷新间隔时间被使用,在语句每次调用时才进行刷新。\n\n> Size:属性可以设置为一个正整数,您需要留意您要缓存对象的大小和环境中可用的内存空间。默认是1024。\n\n> readOnly:属性可以被设置为true 或false。只读缓存将对所有调用者返回同一个实例。因此这些对象都不能被修改,这可以极大的提高性能。可写的缓存将通过序列化来返回一个缓存对象的拷贝。这会比较慢,但是比较安全。所以默认值是false。\n \n\n使用自定义缓存\n\n除了上面已经定义好的缓存方式,您能够通过您自己的缓存实现来完全重写缓存行为,或者通过创建第三方缓存解决方案的适配器。\n\n \n \n这个例子演示了如果自定义缓存实现。由type指定的类必须实现org.mybatis.cache.Cache接口。这个接口是MyBatis框架比较复杂的接口之一,先给个示例:\n\n public interface Cache {\n String getId();\n int getSize();\n void putObject(Object key, Object value);\n Object getObject(Object key);\n boolean hasKey(Object key);\n Object removeObject(Object key);\n void clear();\n ReadWriteLock getReadWriteLock();\n }\n \n\n要配置您的缓存,简单地添加一个公共的JavaBeans 属性到您的缓存实现中,然后通过cache 元素设置属性进行传递,下面示例,将在您的缓存实现上调用一个setCacheFile(String file)方法。\n \n \n \n \n \n\n您可以使用所有简单的JavaBeans属性,MyBatis会自动进行转换。\n\n需要牢记的是一个缓存配置和缓存实例都绑定到一个SQL Map 文件命名空间。因此,所有的这个相同命名空间的语句也都和这个缓存绑定。语句可以修改如何与这个缓存相匹配,或者使用两个简单的属性来完全排除它们自己。默认情况下,语句像下面这样来配置:\n\n