[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"\u002Fresource\u002Fdocument\u002Flist?undefined":3,"\u002Fresource\u002Fdocument\u002Fquery\u002Fpqv38vexmenglk4k?undefined":462,"\u002Fresource\u002Fadvertise\u002Flist?type=all?undefined":465},{"data":4,"status":460,"success":461},[5,148,202,291,332,370,420],{"books":6,"desc":145,"id":8,"image":146,"title":147},[7,40,63,78,93,105,117],{"cateId":8,"chapters":9,"desc":36,"id":11,"time":37,"title":38,"video":39},1,[10,15,18,21,24,27,30,33],{"bookId":11,"id":12,"indexOrder":13,"name":14},24,"8egfulw98v3h680j",0,"JavaSE 笔记（一）走进Java语言",{"bookId":11,"id":16,"indexOrder":13,"name":17},"pew6po6wrou23pk3","JavaSE 笔记（二）面向过程编程",{"bookId":11,"id":19,"indexOrder":13,"name":20},"eldst1fgrbdkmfs7","JavaSE 笔记（三）面向对象基础",{"bookId":11,"id":22,"indexOrder":13,"name":23},"48zphgkpjto8cath","JavaSE 笔记（四）面向对象高级篇",{"bookId":11,"id":25,"indexOrder":13,"name":26},"6r4llai92yc15j98","JavaSE 笔记（五）泛型程序设计",{"bookId":11,"id":28,"indexOrder":13,"name":29},"k6fmxd6qabgkwm9i","JavaSE 笔记（六）集合类与IO",{"bookId":11,"id":31,"indexOrder":13,"name":32},"qrd0xfttsz32gpqg","JavaSE 笔记（七）多线程与反射",{"bookId":11,"id":34,"indexOrder":13,"name":35},"td5tgn04nqmkrryt","JavaSE 笔记（八）GUI程序开发","基于Java25全新录制的SE课程",2025,"JavaSE 核心内容","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV163GGz2E8c",{"cateId":8,"chapters":41,"desc":59,"id":8,"time":60,"title":61,"video":62},[42,44,46,49,51,53,55,57],{"bookId":8,"id":43,"indexOrder":13,"name":14},"ibeeuwsbbi00undq",{"bookId":8,"id":45,"indexOrder":13,"name":17},"dncxjecdv4wciqcp",{"bookId":8,"id":47,"indexOrder":13,"name":48},"jviyz2hsht9ete5k","JavaSE 笔记（三）面向对象基础篇",{"bookId":8,"id":50,"indexOrder":13,"name":23},"qb9i6q9fap7bg1cc",{"bookId":8,"id":52,"indexOrder":13,"name":26},"hnkrjrkm3hjzeq6s",{"bookId":8,"id":54,"indexOrder":13,"name":29},"erpm32wduoaaqmrx",{"bookId":8,"id":56,"indexOrder":13,"name":32},"lfqtvxr7azumcwja",{"bookId":8,"id":58,"indexOrder":13,"name":35},"qs7gqok56gzc6idr","2022年制作的JavaSE版本",2022,"JavaSE 22年旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1YP4y1o75f\u002F",{"cateId":8,"chapters":64,"desc":75,"id":66,"time":60,"title":76,"video":77},[65,69,72],{"bookId":66,"id":67,"indexOrder":13,"name":68},2,"g96k66kczovvbm1i","JVM 笔记（一）走进JVM",{"bookId":66,"id":70,"indexOrder":13,"name":71},"ydd7n3jg8unc3clg","JVM 笔记（二）内存管理",{"bookId":66,"id":73,"indexOrder":13,"name":74},"r9dq37de0kaeauoi","JVM 笔记（三）类与类加载","了解Java的底层运作机制","Java JVM 虚拟机","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Er4y1r7as\u002F",{"cateId":8,"chapters":79,"desc":90,"id":81,"time":60,"title":91,"video":92},[80,84,87],{"bookId":81,"id":82,"indexOrder":13,"name":83},3,"asncyye9ya18gfar","JUC 笔记（一）再谈多线程",{"bookId":81,"id":85,"indexOrder":13,"name":86},"5tr1sm4ho6ygpt9q","JUC 笔记（二）并发编程核心",{"bookId":81,"id":88,"indexOrder":13,"name":89},"1scf51z5300mzxkh","JUC 笔记（三）并发编程进阶","你也可以成为多线程的主宰者","Java JUC 并发编程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1JT4y1S7K8\u002F",{"cateId":8,"chapters":94,"desc":102,"id":96,"time":60,"title":103,"video":104},[95,99],{"bookId":96,"id":97,"indexOrder":13,"name":98},4,"eedesc445ygiqhil","NIO 笔记（一）基础内容",{"bookId":96,"id":100,"indexOrder":13,"name":101},"ndz9t0uunrmfmv4n","NIO 笔记（二）Netty框架专题","编写畅快的高性能网络服务器","Java NIO 网络编程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1ar4y1J7mC\u002F",{"cateId":8,"chapters":106,"desc":114,"id":108,"time":60,"title":115,"video":116},[107,111],{"bookId":108,"id":109,"indexOrder":13,"name":110},5,"9890i8ofuadpwy2b","[扩展篇] Java 9-17新特性介绍",{"bookId":108,"id":112,"indexOrder":13,"name":113},"tsrkqvb6zpmtwh0n","[扩展篇] JavaSE关键字总结 笔记","精彩仍在继续，不要停止脚步","其他内容","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1tU4y1y7Fg\u002F",{"cateId":8,"chapters":118,"desc":141,"id":120,"time":142,"title":143,"video":144},[119,123,126,129,132,135,138],{"bookId":120,"id":121,"indexOrder":13,"name":122},6,"4db9h32opv7imszh","JavaSE 笔记（一）面向过程编程",{"bookId":120,"id":124,"indexOrder":13,"name":125},"c93u3v37br7hgn1q","JavaSE 笔记（二）面向对象基础篇",{"bookId":120,"id":127,"indexOrder":13,"name":128},"yglsjde9gi1jxkcb","JavaSE 笔记（三）泛型与集合类",{"bookId":120,"id":130,"indexOrder":13,"name":131},"ilhi987n986rmvo3","JavaSE 笔记（四）异常机制",{"bookId":120,"id":133,"indexOrder":13,"name":134},"pqv38vexmenglk4k","JavaSE 笔记（五）IO",{"bookId":120,"id":136,"indexOrder":13,"name":137},"jiq41n87i9ia7ilw","JavaSE 笔记（六）多线程",{"bookId":120,"id":139,"indexOrder":13,"name":140},"wn7x2mge9ws79zps","JavaSE 笔记（七）反射","此版本为早期录制的旧版本",2021,"JavaSE 21年旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Gv411T7pi\u002F","包含JavaSE基础路线全部教程笔记，打下坚实的基础","https:\u002F\u002Fpic2.zhimg.com\u002F80\u002Fv2-bf1a927f037a79f4d57d9ae543430a0d_1440w.webp","JavaSE 系列笔记 ☕️",{"books":149,"desc":199,"id":66,"image":200,"title":201},[150,166,178],{"cateId":66,"chapters":151,"desc":162,"id":153,"time":163,"title":164,"video":165},[152,156,159],{"bookId":153,"id":154,"indexOrder":13,"name":155},21,"iqbc2haub31bwqtz","Lombok 极速上手",{"bookId":153,"id":157,"indexOrder":13,"name":158},"ijay2hay19kn1k031","Mybatis 快速上手",{"bookId":153,"id":160,"indexOrder":13,"name":161},"ru4ogh2waocpn4jo","Maven 快速上手","JavaWeb阶段必须扩展知识点",2024,"常用知识讲解","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1gb421J7ok\u002F",{"cateId":66,"chapters":167,"desc":175,"id":169,"time":163,"title":176,"video":177},[168,172],{"bookId":169,"id":170,"indexOrder":13,"name":171},22,"ek20yvb6huhxizx7","JavaWeb 笔记（一）计算机网络基础",{"bookId":169,"id":173,"indexOrder":13,"name":174},"pgevws6w2krkffa4","JavaWeb笔记（二）Java与数据库","全面升级的JavaWeb课程","JavaWeb 网站开发","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1kS421X7rq\u002F",{"cateId":66,"chapters":179,"desc":196,"id":181,"time":142,"title":197,"video":198},[180,184,187,190,193],{"bookId":181,"id":182,"indexOrder":13,"name":183},7,"ggwwj09j2vkfftvd","JavaWeb 笔记（一）Java网络编程",{"bookId":181,"id":185,"indexOrder":13,"name":186},"sauvq105istskjaz","JavaWeb 笔记（二）数据库基础",{"bookId":181,"id":188,"indexOrder":13,"name":189},"xgbeasmvrhxx9tn4","JavaWeb 笔记（三）Java与数据库",{"bookId":181,"id":191,"indexOrder":13,"name":192},"k7dfwua3bsezvw9q","JavaWeb 笔记（四）前端基础",{"bookId":181,"id":194,"indexOrder":13,"name":195},"ycpagby2v7j4p728","JavaWeb 笔记（五）后端开发","搭建属于自己的Web网站","JavaWeb 旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1CL4y1i7qR\u002F","包含JavaWeb路线全套笔记，从零开始搭建自己的网站！","https:\u002F\u002Fpic3.zhimg.com\u002F80\u002Fv2-df3b38e3012258ed70c23b586309e3f6_1440w.webp","JavaWeb 系列笔记 🚛",{"books":203,"desc":288,"id":81,"image":289,"title":290},[204,220,235,255,273],{"cateId":81,"chapters":205,"desc":216,"id":207,"time":217,"title":218,"video":219},[206,210,213],{"bookId":207,"id":208,"indexOrder":13,"name":209},8,"h7sjo5oy0l03607e","SSM笔记（一）Spring基础",{"bookId":207,"id":211,"indexOrder":13,"name":212},"eve8gq72qmdb46sg","SSM笔记（二）SpringMvc基础",{"bookId":207,"id":214,"indexOrder":13,"name":215},"63v73g0zh1qlr6fk","SSM笔记（三）SpringSecurity基础","Spring的探索之路从这里开始",2023,"JavaSSM 基础部分","[\"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Kv4y1x7is\u002F\", \"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Lh4y1M7kx\u002F\", \"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1fV411M7aS\u002F\"]",{"cateId":81,"chapters":221,"desc":232,"id":223,"time":217,"title":233,"video":234},[222,226,229],{"bookId":223,"id":224,"indexOrder":13,"name":225},16,"0k66v5r6slsfuog4","SpringBoot笔记（一）核心内容",{"bookId":223,"id":227,"indexOrder":13,"name":228},"bqlrnc2yvkaxo8s1","SpringBoot笔记（二）数据交互",{"bookId":223,"id":230,"indexOrder":13,"name":231},"wci9lb9tgea866jt","SpringBoot笔记（三）前后端分离","SpringBoot全新重制版","SpringBoot 新版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1xu4y1m7UP\u002F",{"cateId":81,"chapters":236,"desc":252,"id":238,"time":60,"title":253,"video":254},[237,240,243,246,249],{"bookId":238,"id":239,"indexOrder":13,"name":225},9,"e43gl1ilygps032v",{"bookId":238,"id":241,"indexOrder":13,"name":242},"emnmd8nzfdb3hr50","SpringBoot笔记（二）Git版本控制",{"bookId":238,"id":244,"indexOrder":13,"name":245},"jjlolj5igvttvyhv","SpringBoot笔记（三）Redis数据库",{"bookId":238,"id":247,"indexOrder":13,"name":248},"skgr4ivb5curdoux","SpringBoot笔记（四）其他框架介绍",{"bookId":238,"id":250,"indexOrder":13,"name":251},"le91fqhu4dqui1k4","SpringBoot笔记（五）Linux系统","逐步走向企业级开发","SpringBoot 旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1UL411V7f3\u002F",{"cateId":81,"chapters":256,"desc":270,"id":258,"time":60,"title":271,"video":272},[257,261,264,267],{"bookId":258,"id":259,"indexOrder":13,"name":260},10,"oejzo0l77zeb6a7e","SpringCloud笔记（一）微服务基础",{"bookId":258,"id":262,"indexOrder":13,"name":263},"f6eya9taaelsl35p","SpringCloud笔记（二）微服务进阶",{"bookId":258,"id":265,"indexOrder":13,"name":266},"35v1hbsfcdgagdnw","SpringCloud笔记（三）微服务应用",{"bookId":258,"id":268,"indexOrder":13,"name":269},"a782u84512tyuo1m","SpringCloud笔记（四）消息队列","体验微服务架构带来的魅力","SpringCloud 进阶","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1AL4y1j7RY\u002F",{"cateId":81,"chapters":274,"desc":285,"id":276,"time":142,"title":286,"video":287},[275,278,280,282],{"bookId":276,"id":277,"indexOrder":13,"name":209},11,"efjw75u8a251qxk5",{"bookId":276,"id":279,"indexOrder":13,"name":212},"guc134xb7sl78vju",{"bookId":276,"id":281,"indexOrder":13,"name":215},"u8ekxxucowr2b1tm",{"bookId":276,"id":283,"indexOrder":13,"name":284},"vkpmw9wbej21nei6","SSM笔记（四）MySQL进阶","此教程为2021年旧版教程","JavaSSM 旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1xL4y1H7Tq\u002F","包含Spring全套框架笔记，从开始到Spring Boot，以及众多运维小知识。","https:\u002F\u002Fpic4.zhimg.com\u002F80\u002Fv2-28c3144421220d7c048703281bc34f63_1440w.webp","Spring 系列笔记 🍏",{"books":292,"desc":329,"id":96,"image":330,"title":331},[293,308],{"cateId":96,"chapters":294,"desc":305,"id":296,"time":60,"title":306,"video":307},[295,299,302],{"bookId":296,"id":297,"indexOrder":13,"name":298},12,"jd3e8u5cmvx5gco6","C语言（一）计算机思维导论",{"bookId":296,"id":300,"indexOrder":13,"name":301},"lqv77apvx82nkkio","C语言（二）基础语法",{"bookId":296,"id":303,"indexOrder":13,"name":304},"xb0b9t37gyv96xns","C语言（三）高级特性","包含高等院校需要教授的全部内容","C语言程序设计","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Cr4y137os\u002F",{"cateId":96,"chapters":309,"desc":326,"id":311,"time":60,"title":327,"video":328},[310,314,317,320,323],{"bookId":311,"id":312,"indexOrder":13,"name":313},13,"8a046ps2e4w6k4py","数据结构与算法（一）线性结构篇",{"bookId":311,"id":315,"indexOrder":13,"name":316},"3ma8db91f9zrnkja","数据结构与算法（二）树形结构篇",{"bookId":311,"id":318,"indexOrder":13,"name":319},"0lsjm59k7cgu4tpr","数据结构与算法（三）散列表篇",{"bookId":311,"id":321,"indexOrder":13,"name":322},"0qzy7bogo0g2pusa","数据结构与算法（四）图结构篇",{"bookId":311,"id":324,"indexOrder":13,"name":325},"6gmcxcikcilyxblj","数据结构与算法（五）排序算法篇","虽然很难，但是它是考研必学科目","数据结构与算法","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV13W4y127Ey\u002F","你的内心一直有一个坚定的声音在告诉你，一定要考上一名研究生，向着未来前进吧！","https:\u002F\u002Fpic2.zhimg.com\u002F80\u002Fv2-ac128404efb29ce1c9d1ccc61024f1d1_1440w.webp","C语言 系列笔记 🥬",{"books":333,"desc":367,"id":108,"image":368,"title":369},[334,349,358],{"cateId":108,"chapters":335,"desc":346,"id":337,"time":163,"title":347,"video":348},[336,340,343],{"bookId":337,"id":338,"indexOrder":13,"name":339},17,"urw2e6gg1lprv65w","Kotlin（一）基础语法",{"bookId":337,"id":341,"indexOrder":13,"name":342},"t7lnl87f74f3v1ju","Kotlin（二）类与对象",{"bookId":337,"id":344,"indexOrder":13,"name":345},"v1zzvki0knb1xvml","Kotlin（三）高级特性","包含Kotlin语言完整基础部分","Kotlin程序设计基础","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1P94y1c7tV\u002F",{"cateId":108,"chapters":350,"desc":355,"id":352,"time":163,"title":356,"video":357},[351],{"bookId":352,"id":353,"indexOrder":13,"name":354},18,"ovbzpe7065bye1st","Kotlin扩展（一）","包含Kotlin额外扩展知识","Kotlin扩展篇","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Hg4y1m7Ca\u002F",{"cateId":108,"chapters":359,"desc":364,"id":361,"time":163,"title":365,"video":366},[360],{"bookId":361,"id":362,"indexOrder":13,"name":363},19,"3at7ybv04dmjc0wp","Gradle基础教程","Gradle配置教程（Kotlin）","Gradle教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Fc411x7xF\u002F","Kotlin让JVM平台焕发新的生机，让语言的表达更加优美","https:\u002F\u002Fpic2.zhimg.com\u002F80\u002Fv2-be815568f7c79c64cdaa171b0409786d_1440w.webp","Kotlin 系列笔记 ☘️",{"books":371,"desc":418,"id":120,"title":419},[372,391,403],{"cateId":120,"chapters":373,"desc":387,"id":375,"time":388,"title":389,"video":390},[374,378,381,384],{"bookId":375,"id":376,"indexOrder":13,"name":377},26,"zjf5qapwqtqiohcn","JavaScript笔记（一）基础语法",{"bookId":375,"id":379,"indexOrder":13,"name":380},"95jc6sjyjwcp9pvp","JavaScript笔记（二）核心知识",{"bookId":375,"id":382,"indexOrder":13,"name":383},"j35cdc1qz8dzq7pn","JavaScript笔记（三）进阶知识",{"bookId":375,"id":385,"indexOrder":13,"name":386},"sdhodlihphnpcg37","JavaScript笔记（四）前端基础","包含JavaScript最新语法规范讲解",2026,"JavaScript教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1xq6gBgESU",{"cateId":120,"chapters":392,"desc":400,"id":394,"time":37,"title":401,"video":402},[393,397],{"bookId":394,"id":395,"indexOrder":13,"name":396},23,"bsisgazdftiz3o9c","HTML5笔记（一）基础内容",{"bookId":394,"id":398,"indexOrder":13,"name":399},"njol93fs34gfwuzf","HTML5笔记（二）高级内容","包含HTML基础内容和相关知识点","HTML5核心教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1BrBiYNEWg",{"cateId":120,"chapters":404,"desc":415,"id":406,"time":37,"title":416,"video":417},[405,409,412],{"bookId":406,"id":407,"indexOrder":13,"name":408},25,"jo74ciirtg8wh90y","CSS笔记（一）基础入门",{"bookId":406,"id":410,"indexOrder":13,"name":411},"ap5ixyomoejuw4ue","CSS笔记（二）盒模型和布局",{"bookId":406,"id":413,"indexOrder":13,"name":414},"4djgk5xy1lzpiuf2","CSS笔记（三）变换和过渡","包含CSS3基础内容和相关知识点","CSS3核心教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1sQeEzFEKi","包含Web前端学习路径全部教程笔记，打下坚实的基础","Web前端 系列笔记",{"books":421,"desc":458,"id":423,"image":368,"title":459},[422,432,450],{"cateId":423,"chapters":424,"desc":429,"id":426,"time":163,"title":430,"video":431},100,[425],{"bookId":426,"id":427,"indexOrder":13,"name":428},20,"o0ab271mkdsas87","Markdown基础语法","编写简洁而又优美的文档","Markdown教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1eJ4m157kC",{"cateId":423,"chapters":433,"desc":447,"id":435,"time":60,"title":448,"video":449},[434,438,441,444],{"bookId":435,"id":436,"indexOrder":13,"name":437},14,"6386mh7anqt4tzyv","设计模式（一）面向对象设计原则",{"bookId":435,"id":439,"indexOrder":13,"name":440},"8ftkb38wfn6ox0ug","设计模式（二）创建型",{"bookId":435,"id":442,"indexOrder":13,"name":443},"i1msql1k8y70etey","设计模式（三）结构型",{"bookId":435,"id":445,"indexOrder":13,"name":446},"5434a3cyyjvwhs8s","设计模式（四）行为型","使你的编码水平得到质的飞跃","设计模式系列","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1u3411P7Na\u002F",{"cateId":423,"chapters":451,"desc":456,"id":453,"time":60,"title":457},[452],{"bookId":453,"id":454,"indexOrder":13,"name":455},15,"zj9uvg0sp3b0sok8","Docker 容器技术 笔记","这里包含其他中间件课程笔记","其他中间件笔记","我们对知识的探索从未停止，只有不断地学习，才能走向美好的未来！","其他笔记分类 🌽",200,true,{"data":463,"status":460,"success":461},{"bookId":120,"content":464,"id":133,"indexOrder":108,"name":134},"# Java I\u002FO\n\n**注意：** 这块会涉及到**操作系统**和**计算机组成原理**相关内容。\n\nI\u002FO简而言之，就是输入输出，那么为什么会有I\u002FO呢？其实I\u002FO无时无刻都在我们的身边，比如读取硬盘上的文件，网络文件传输，鼠标键盘输入，也可以是接受单片机发回的数据，而能够支持这些操作的设备就是I\u002FO设备。\n\n我们可以大致看一下整个计算机的总线结构：\n\n![img](https:\u002F\u002Fs2.loli.net\u002F2023\u002F08\u002F14\u002FVHOBIXvulUsMYRT.jpg)\n\n常见的I\u002FO设备一般是鼠标、键盘这类通过USB进行传输的外设或者是通过Sata接口或是M.2连接的硬盘。一般情况下，这些设备是由CPU发出指令通过南桥芯片间接进行控制，而不是由CPU直接操作。\n\n而我们在程序中，想要读取这些外部连接的I\u002FO设备中的内容，就需要将数据传输到内存中。而需要实现这样的操作，单单凭借一个小的程序是无法做到的，而操作系统（如：Windows\u002FLinux\u002FMacOS）就是专门用于控制和管理计算机硬件和软件资源的软件，我们需要读取一个IO设备的内容时，可以向操作系统发出请求，由操作系统帮助我们来和底层的硬件交互以完成我们的读取\u002F写入请求。从读取硬盘文件的角度来说，不同的操作系统有着不同的文件系统（也就是文件在硬盘中的存储排列方式，如Windows就是NTFS、MacOS就是APFS），硬盘只能存储一个个0和1这样的二进制数据，至于0和1如何排列，各自又代表什么意思，就是由操作系统的文件系统来决定的。从网络通信角度来说，网络信号通过网卡等设备翻译为二进制信号，再交给系统进行读取，最后再由操作系统来给到程序。\n\nJDK提供了一套用于IO操作的框架，根据流的传输方向和读取单位，分为字节流InputStream和OutputStream以及字符流Reader和Writer，当然，这里的Stream并不是前面集合框架认识的Stream，这里的流指的是数据流，通过流，我们就可以一直从流中读取数据，直到读取到尽头，或是不断向其中写入数据，直到我们写入完成。而这类IO就是我们所说的BIO，\n\n字节流一次读取一个字节，也就是一个`byte`的大小，而字符流顾名思义，就是一次读取一个字符，也就是一个`char`的大小（在读取纯文本文件的时候更加适合），有关这两种流，会在后面详细介绍，这个章节我们需要学习16个关键的流。\n\n## 文件流\n\n要学习和使用IO，首先就要从最易于理解的读取文件开始说起。\n\n### 文件字节流\n\n首先介绍一下FileInputStream，通过它来获取文件的输入流。\n\n```java\npublic static void main(String[] args) {\n    try {\n        FileInputStream inputStream = new FileInputStream(\"路径\");\n        \u002F\u002F路径支持相对路径和绝对路径\n    } catch (FileNotFoundException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n相对路径是在当前运行的路径下寻找文件，而绝对路径，是从根目录开始寻找。路径分割符支持使用`\u002F`或是`\\\\`，但是不能写为`\\`因为它是转义字符！\n\n在使用完成一个流之后，必须关闭这个流来完成对资源的释放，否则资源会被一直占用！\n\n```java\npublic static void main(String[] args) {\n    FileInputStream inputStream = null;    \u002F\u002F定义可以先放在try外部\n    try {\n        inputStream = new FileInputStream(\"路径\");\n    } catch (FileNotFoundException e) {\n        e.printStackTrace();\n    } finally {\n        try {    \u002F\u002F建议在finally中进行，因为这个是任何情况都必须要执行的！\n            if(inputStream != null) inputStream.close();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n虽然这样的写法才是最保险的，但是显得过于繁琐了，尤其是finally中再次嵌套了一个try-catch块，因此在JDK1.7新增了try-with-resource语法，用于简化这样的写法（本质上还是和这样的操作一致，只是换了个写法）\n\n```java\npublic static void main(String[] args) {\n\n    \u002F\u002F注意，这种语法只支持实现了AutoCloseable接口的类！\n    try(FileInputStream inputStream = new FileInputStream(\"路径\")) {   \u002F\u002F直接在try()中定义要在完成之后释放的资源\n\n    } catch (IOException e) {   \u002F\u002F这里变成IOException是因为调用close()可能会出现，而FileNotFoundException是继承自IOException的\n        e.printStackTrace();\n    }\n    \u002F\u002F无需再编写finally语句块，因为在最后自动帮我们调用了close()\n}\n```\n\n之后为了方便，我们都使用此语法进行教学。\n\n```java\npublic static void main(String[] args) {\n    \u002F\u002Ftest.txt：a\n    try(FileInputStream inputStream = new FileInputStream(\"test.txt\")) {\n        \u002F\u002F使用read()方法进行字符读取\n        System.out.println((char) inputStream.read());  \u002F\u002F读取一个字节的数据（英文字母只占1字节，中文占2字节）\n        System.out.println(inputStream.read());   \u002F\u002F唯一一个字节的内容已经读完了，再次读取返回-1表示没有内容了\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n使用read可以直接读取一个字节的数据，注意，流的内容是有限的，读取一个少一个！我们如果想一次性全部读取的话，可以直接使用一个while循环来完成：\n\n```java\npublic static void main(String[] args) {\n    \u002F\u002Ftest.txt：abcd\n    try(FileInputStream inputStream = new FileInputStream(\"test.txt\")) {\n        int tmp;\n        while ((tmp = inputStream.read()) != -1){   \u002F\u002F通过while循环来一次性读完内容\n            System.out.println((char)tmp);\n        }\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n使用方法能查看当前可读的剩余字节数量（注意：并不一定真实的数据量就是这么多，尤其是在网络I\u002FO操作时，这个方法只能进行一个预估也可以说是暂时能一次性读取的数量）\n\n```java\ntry(FileInputStream inputStream = new FileInputStream(\"test.txt\")) {\n    System.out.println(inputStream.available());  \u002F\u002F查看剩余数量\n}catch (IOException e){\n    e.printStackTrace();\n}\n```\n\n当然，一个一个读取效率太低了，那能否一次性全部读取呢？我们可以预置一个合适容量的byte[]数组来存放。\n\n```java\npublic static void main(String[] args) {\n    \u002F\u002Ftest.txt：abcd\n    try(FileInputStream inputStream = new FileInputStream(\"test.txt\")) {\n        byte[] bytes = new byte[inputStream.available()];   \u002F\u002F我们可以提前准备好合适容量的byte数组来存放\n        System.out.println(inputStream.read(bytes));   \u002F\u002F一次性读取全部内容（返回值是读取的字节数）\n        System.out.println(new String(bytes));   \u002F\u002F通过String(byte[])构造方法得到字符串\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n也可以控制要读取数量：\n\n```java\nSystem.out.println(inputStream.read(bytes, 1, 2));   \u002F\u002F第二个参数是从给定数组的哪个位置开始放入内容，第三个参数是读取流中的字节数\n```\n\n**注意**：一次性读取同单个读取一样，当没有任何数据可读时，依然会返回-1\n\n通过`skip()`方法可以跳过指定数量的字节：\n\n```java\npublic static void main(String[] args) {\n    \u002F\u002Ftest.txt：abcd\n    try(FileInputStream inputStream = new FileInputStream(\"test.txt\")) {\n        System.out.println(inputStream.skip(1));\n        System.out.println((char) inputStream.read());   \u002F\u002F跳过了一个字节\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n注意：FileInputStream是不支持`reset()`的，虽然有这个方法，但是这里先不提及。\n\n既然有输入流，那么文件输出流也是必不可少的：\n\n```java\npublic static void main(String[] args) {\n    \u002F\u002F输出流也需要在最后调用close()方法，并且同样支持try-with-resource\n    try(FileOutputStream outputStream = new FileOutputStream(\"output.txt\")) {\n        \u002F\u002F注意：若此文件不存在，会直接创建这个文件！\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n输出流没有`read()`操作而是`write()`操作，使用方法同输入流一样，只不过现在的方向变为我们向文件里写入内容：\n\n```java\npublic static void main(String[] args) {\n    try(FileOutputStream outputStream = new FileOutputStream(\"output.txt\")) {\n        outputStream.write('c');   \u002F\u002F同read一样，可以直接写入内容\n      \toutputStream.write(\"lbwnb\".getBytes());   \u002F\u002F也可以直接写入byte[]\n      \toutputStream.write(\"lbwnb\".getBytes(), 0, 1);  \u002F\u002F同上输入流\n      \toutputStream.flush();  \u002F\u002F建议在最后执行一次刷新操作（强制写入）来保证数据正确写入到硬盘文件中\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n那么如果是我只想在文件尾部进行追加写入数据呢？我们可以调用另一个构造方法来实现：\n\n```java\npublic static void main(String[] args) {\n    try(FileOutputStream outputStream = new FileOutputStream(\"output.txt\", true)) {\n        outputStream.write(\"lb\".getBytes());   \u002F\u002F现在只会进行追加写入，而不是直接替换原文件内容\n        outputStream.flush();\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n利用输入流和输出流，就可以轻松实现文件的拷贝了：\n\n```java\npublic static void main(String[] args) {\n    try(FileOutputStream outputStream = new FileOutputStream(\"output.txt\");\n        FileInputStream inputStream = new FileInputStream(\"test.txt\")) {   \u002F\u002F可以写入多个\n        byte[] bytes = new byte[10];    \u002F\u002F使用长度为10的byte[]做传输媒介\n        int tmp;   \u002F\u002F存储本地读取字节数\n        while ((tmp = inputStream.read(bytes)) != -1){   \u002F\u002F直到读取完成为止\n            outputStream.write(bytes, 0, tmp);    \u002F\u002F写入对应长度的数据到输出流\n        }\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n### 文件字符流\n\n字符流不同于字节，字符流是以一个具体的字符进行读取，因此它只适合读纯文本的文件，如果是其他类型的文件不适用：\n\n```java\npublic static void main(String[] args) {\n    try(FileReader reader = new FileReader(\"test.txt\")){\n      \treader.skip(1);   \u002F\u002F现在跳过的是一个字符\n        System.out.println((char) reader.read());   \u002F\u002F现在是按字符进行读取，而不是字节，因此可以直接读取到中文字符\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n同理，字符流只支持`char[]`类型作为存储：\n\n```java\npublic static void main(String[] args) {\n    try(FileReader reader = new FileReader(\"test.txt\")){\n        char[] str = new char[10];\n        reader.read(str);\n        System.out.println(str);   \u002F\u002F直接读取到char[]中\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n既然有了Reader肯定也有Writer：\n\n```java\npublic static void main(String[] args) {\n    try(FileWriter writer = new FileWriter(\"output.txt\")){\n      \twriter.getEncoding();   \u002F\u002F支持获取编码（不同的文本文件可能会有不同的编码类型）\n       writer.write('牛');\n       writer.append('牛');   \u002F\u002F其实功能和write一样\n      \twriter.flush();   \u002F\u002F刷新\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n我们发现不仅有`write()`方法，还有一个`append()`方法，但是实际上他们效果是一样的，看源码：\n\n```java\n\u002F**\n * Appends the specified character to this writer.\n *\n * \u003Cp> An invocation of this method of the form \u003Ctt>out.append(c)\u003C\u002Ftt>\n * behaves in exactly the same way as the invocation\n *\n * \u003Cpre>\n *     out.write(c) \u003C\u002Fpre>\n *\n * @param  c\n *         The 16-bit character to append\n *\n * @return  This writer\n *\n * @throws  IOException\n *          If an I\u002FO error occurs\n *\n * @since 1.5\n *\u002F\npublic Writer append(char c) throws IOException {\n    write(c);\n    return this;\n}\n```\n\nappend支持像StringBuilder那样的链式调用，返回的是Writer对象本身。\n\n**练习**：尝试一下用Reader和Writer来拷贝纯文本文件\n\n### File类\n\nFile类专门用于表示一个文件或文件夹，只不过它只是代表这个文件，但并不是这个文件本身。通过File对象，可以更好地管理和操作硬盘上的文件。\n\n```java\npublic static void main(String[] args) {\n    File file = new File(\"test.txt\");   \u002F\u002F直接创建文件对象，可以是相对路径，也可以是绝对路径\n    System.out.println(file.exists());   \u002F\u002F此文件是否存在\n    System.out.println(file.length());   \u002F\u002F获取文件的大小\n    System.out.println(file.isDirectory());   \u002F\u002F是否为一个文件夹\n    System.out.println(file.canRead());   \u002F\u002F是否可读\n    System.out.println(file.canWrite());   \u002F\u002F是否可写\n    System.out.println(file.canExecute());   \u002F\u002F是否可执行\n}\n```\n\n通过File对象，我们就能快速得到文件的所有信息，如果是文件夹，还可以获取文件夹内部的文件列表等内容：\n\n```java\nFile file = new File(\"\u002F\");\nSystem.out.println(Arrays.toString(file.list()));   \u002F\u002F快速获取文件夹下的文件名称列表\nfor (File f : file.listFiles()){   \u002F\u002F所有子文件的File对象\n    System.out.println(f.getAbsolutePath());   \u002F\u002F获取文件的绝对路径\n}\n```\n\n如果我们希望读取某个文件的内容，可以直接将File作为参数传入字节流或是字符流：\n\n```java\nFile file = new File(\"test.txt\");\ntry (FileInputStream inputStream = new FileInputStream(file)){   \u002F\u002F直接做参数\n    System.out.println(inputStream.available());\n}catch (IOException e){\n    e.printStackTrace();\n}\n```\n\n**练习**：尝试拷贝文件夹下的所有文件到另一个文件夹\n\n***\n\n## 缓冲流\n\n虽然普通的文件流读取文件数据非常便捷，但是每次都需要从外部I\u002FO设备去获取数据，由于外部I\u002FO设备的速度一般都达不到内存的读取速度，很有可能造成程序反应迟钝，因此性能还不够高，而缓冲流正如其名称一样，它能够提供一个缓冲，提前将部分内容存入内存（缓冲区）在下次读取时，如果缓冲区中存在此数据，则无需再去请求外部设备。同理，当向外部设备写入数据时，也是由缓冲区处理，而不是直接向外部设备写入。\n\n![img](https:\u002F\u002Fs2.loli.net\u002F2023\u002F08\u002F14\u002FUpicNGRSy7Zz35f.jpg)\n\n### 缓冲字节流\n\n要创建一个缓冲字节流，只需要将原本的流作为构造参数传入BufferedInputStream即可：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(\"test.txt\"))){   \u002F\u002F传入FileInputStream\n        System.out.println((char) bufferedInputStream.read());   \u002F\u002F操作和原来的流是一样的\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n实际上进行I\u002FO操作的并不是BufferedInputStream，而是我们传入的FileInputStream，而BufferedInputStream虽然有着同样的方法，但是进行了一些额外的处理然后再调用FileInputStream的同名方法，这样的写法称为`装饰者模式`\n\n```java\npublic void close() throws IOException {\n    byte[] buffer;\n    while ( (buffer = buf) != null) {\n        if (bufUpdater.compareAndSet(this, buffer, null)) {  \u002F\u002FCAS无锁算法，并发会用到，暂时不管\n            InputStream input = in;\n            in = null;\n            if (input != null)\n                input.close();\n            return;\n        }\n        \u002F\u002F Else retry in case a new buf was CASed in fill()\n    }\n}\n```\n\n实际上这种模式是父类FilterInputStream提供的规范，后面我们还会讲到更多FilterInputStream的子类。\n\n我们可以发现在BufferedInputStream中还存在一个专门用于缓存的数组：\n\n```java\n\u002F**\n * The internal buffer array where the data is stored. When necessary,\n * it may be replaced by another array of\n * a different size.\n *\u002F\nprotected volatile byte buf[];\n```\n\nI\u002FO操作一般不能重复读取内容（比如键盘发送的信号，主机接收了就没了），而缓冲流提供了缓冲机制，一部分内容可以被暂时保存，BufferedInputStream支持`reset()`和`mark()`操作，首先我们来看看`mark()`方法的介绍：\n\n```java\n\u002F**\n * Marks the current position in this input stream. A subsequent\n * call to the \u003Ccode>reset\u003C\u002Fcode> method repositions this stream at\n * the last marked position so that subsequent reads re-read the same bytes.\n * \u003Cp>\n * The \u003Ccode>readlimit\u003C\u002Fcode> argument tells this input stream to\n * allow that many bytes to be read before the mark position gets\n * invalidated.\n * \u003Cp>\n * This method simply performs \u003Ccode>in.mark(readlimit)\u003C\u002Fcode>.\n *\n * @param   readlimit   the maximum limit of bytes that can be read before\n *                      the mark position becomes invalid.\n * @see     java.io.FilterInputStream#in\n * @see     java.io.FilterInputStream#reset()\n *\u002F\npublic synchronized void mark(int readlimit) {\n    in.mark(readlimit);\n}\n```\n\n当调用`mark()`之后，输入流会以某种方式保留之后读取的`readlimit`数量的内容，当读取的内容数量超过`readlimit`则之后的内容不会被保留，当调用`reset()`之后，会使得当前的读取位置回到`mark()`调用时的位置。\n\n```java\npublic static void main(String[] args) {\n    try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(\"test.txt\"))){\n        bufferedInputStream.mark(1);   \u002F\u002F只保留之后的1个字符\n        System.out.println((char) bufferedInputStream.read());\n        System.out.println((char) bufferedInputStream.read());\n        bufferedInputStream.reset();   \u002F\u002F回到mark时的位置\n        System.out.println((char) bufferedInputStream.read());\n        System.out.println((char) bufferedInputStream.read());\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n我们发现虽然后面的部分没有保存，但是依然能够正常读取，其实`mark()`后保存的读取内容是取`readlimit`和BufferedInputStream类的缓冲区大小两者中的最大值，而并非完全由`readlimit`确定。因此我们限制一下缓冲区大小，再来观察一下结果：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(\"test.txt\"), 1)){  \u002F\u002F将缓冲区大小设置为1\n        bufferedInputStream.mark(1);   \u002F\u002F只保留之后的1个字符\n        System.out.println((char) bufferedInputStream.read());\n        System.out.println((char) bufferedInputStream.read());   \u002F\u002F已经超过了readlimit，继续读取会导致mark失效\n        bufferedInputStream.reset();   \u002F\u002Fmark已经失效，无法reset()\n        System.out.println((char) bufferedInputStream.read());\n        System.out.println((char) bufferedInputStream.read());\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n了解完了BufferedInputStream之后，我们再来看看BufferedOutputStream，其实和BufferedInputStream原理差不多，只是反向操作：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(\"output.txt\"))){\n        outputStream.write(\"lbwnb\".getBytes());\n        outputStream.flush();\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n操作和FileOutputStream一致，这里就不多做介绍了。\n\n### 缓冲字符流\n\n缓存字符流和缓冲字节流一样，也有一个专门的缓冲区，BufferedReader构造时需要传入一个Reader对象：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedReader reader = new BufferedReader(new FileReader(\"test.txt\"))){\n        System.out.println((char) reader.read());\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n使用和reader也是一样的，内部也包含一个缓存数组：\n\n```java\nprivate char cb[];\n```\n\n相比Reader更方便的是，它支持按行读取：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedReader reader = new BufferedReader(new FileReader(\"test.txt\"))){\n        System.out.println(reader.readLine());   \u002F\u002F按行读取\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n读取后直接得到一个字符串，当然，它还能把每一行内容依次转换为集合类提到的Stream流：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedReader reader = new BufferedReader(new FileReader(\"test.txt\"))){\n        reader\n                .lines()\n                .limit(2)\n                .distinct()\n                .sorted()\n                .forEach(System.out::println);\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n它同样也支持`mark()`和`reset()`操作：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedReader reader = new BufferedReader(new FileReader(\"test.txt\"))){\n        reader.mark(1);\n        System.out.println((char) reader.read());\n        reader.reset();\n        System.out.println((char) reader.read());\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\nBufferedReader处理纯文本文件时就更加方便了，BufferedWriter在处理时也同样方便：\n\n```java\npublic static void main(String[] args) {\n    try (BufferedWriter reader = new BufferedWriter(new FileWriter(\"output.txt\"))){\n        reader.newLine();   \u002F\u002F使用newLine进行换行\n        reader.write(\"汉堡做滴彳亍不彳亍\");   \u002F\u002F可以直接写入一个字符串\n      \treader.flush();   \u002F\u002F清空缓冲区\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n***\n\n## 转换流\n\n有时会遇到这样一个很麻烦的问题：我这里读取的是一个字符串或是一个个字符，但是我只能往一个OutputStream里输出，但是OutputStream又只支持byte类型，如果要往里面写入内容，进行数据转换就会很麻烦，那么能否有更加简便的方式来做这样的事情呢？\n\n```java\npublic static void main(String[] args) {\n    try(OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(\"test.txt\"))){  \u002F\u002F虽然给定的是FileOutputStream，但是现在支持以Writer的方式进行写入\n        writer.write(\"lbwnb\");   \u002F\u002F以操作Writer的样子写入OutputStream\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n同样的，我们现在只拿到了一个InputStream，但是我们希望能够按字符的方式读取，我们就可以使用InputStreamReader来帮助我们实现：\n\n```java\npublic static void main(String[] args) {\n    try(InputStreamReader reader = new InputStreamReader(new FileInputStream(\"test.txt\"))){  \u002F\u002F虽然给定的是FileInputStream，但是现在支持以Reader的方式进行读取\n        System.out.println((char) reader.read());\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\nInputStreamReader和OutputStreamWriter本质也是Reader和Writer，因此可以直接放入BufferedReader来实现更加方便的操作。\n\n***\n\n## 打印流\n\n打印流其实我们从一开始就在使用了，比如`System.out`就是一个PrintStream，PrintStream也继承自FilterOutputStream类因此依然是装饰我们传入的输出流，但是它存在自动刷新机制，例如当向PrintStream流中写入一个字节数组后自动调用`flush()`方法。PrintStream也永远不会抛出异常，而是使用内部检查机制`checkError()`方法进行错误检查。最方便的是，它能够格式化任意的类型，将它们以字符串的形式写入到输出流。\n\n```java\npublic final static PrintStream out = null;\n```\n\n可以看到`System.out`也是PrintStream，不过默认是向控制台打印，我们也可以让它向文件中打印：\n\n```java\npublic static void main(String[] args) {\n    try(PrintStream stream = new PrintStream(new FileOutputStream(\"test.txt\"))){\n        stream.println(\"lbwnb\");   \u002F\u002F其实System.out就是一个PrintStream\n    }catch (IOException e){\n        e.printStackTrace();\n    }\n}\n```\n\n我们平时使用的`println`方法就是PrintStream中的方法，它会直接打印基本数据类型或是调用对象的`toString()`方法得到一个字符串，并将字符串转换为字符，放入缓冲区再经过转换流输出到给定的输出流上。\n\n![img](https:\u002F\u002Fs2.loli.net\u002F2023\u002F08\u002F14\u002FBNg21b7woepEVkY.png)\n\n因此实际上内部还包含这两个内容：\n\n```java\n\u002F**\n * Track both the text- and character-output streams, so that their buffers\n * can be flushed without flushing the entire stream.\n *\u002F\nprivate BufferedWriter textOut;\nprivate OutputStreamWriter charOut;\n```\n\n与此相同的还有一个PrintWriter，不过他们的功能基本一致，PrintWriter的构造方法可以接受一个Writer作为参数，这里就不再做过多阐述了。\n\n***\n\n## 数据流\n\n数据流DataInputStream也是FilterInputStream的子类，同样采用装饰者模式，最大的不同是它支持基本数据类型的直接读取：\n\n```java\npublic static void main(String[] args) {\n    try (DataInputStream dataInputStream = new DataInputStream(new FileInputStream(\"test.txt\"))){\n        System.out.println(dataInputStream.readBoolean());   \u002F\u002F直接将数据读取为任意基本数据类型\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n用于写入基本数据类型：\n\n```java\npublic static void main(String[] args) {\n    try (DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(\"output.txt\"))){\n        dataOutputStream.writeBoolean(false);\n    }catch (IOException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n注意，写入的是二进制数据，并不是写入的字符串，使用DataInputStream可以读取，一般他们是配合一起使用的。\n\n## 对象流\n\n既然基本数据类型能够读取和写入基本数据类型，那么能否将对象也支持呢？ObjectOutputStream不仅支持基本数据类型，通过对对象的序列化操作，以某种格式保存对象，来支持对象类型的IO，注意：它不是继承自FilterInputStream的。\n\n```java\npublic static void main(String[] args) {\n    try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(\"output.txt\"));\n         ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(\"output.txt\"))){\n        People people = new People(\"lbw\");\n        outputStream.writeObject(people);\n      \toutputStream.flush();\n        people = (People) inputStream.readObject();\n        System.out.println(people.name);\n    }catch (IOException | ClassNotFoundException e) {\n        e.printStackTrace();\n    }\n}\n\nstatic class People implements Serializable{   \u002F\u002F必须实现Serializable接口才能被序列化\n    String name;\n\n    public People(String name){\n        this.name = name;\n    }\n}\n```\n\n在我们后续的操作中，有可能会使得这个类的一些结构发生变化，而原来保存的数据只适用于之前版本的这个类，因此我们需要一种方法来区分类的不同版本：\n\n```java\nstatic class People implements Serializable{\n    private static final long serialVersionUID = 123456;   \u002F\u002F在序列化时，会被自动添加这个属性，它代表当前类的版本，我们也可以手动指定版本。\n\n    String name;\n\n    public People(String name){\n        this.name = name;\n    }\n}\n```\n\n当发生版本不匹配时，会无法反序列化为对象：\n\n```java\njava.io.InvalidClassException: com.test.Main$People; local class incompatible: stream classdesc serialVersionUID = 123456, local class serialVersionUID = 1234567\n\tat java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)\n\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2003)\n\tat java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1850)\n\tat java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2160)\n\tat java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1667)\n\tat java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)\n\tat java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)\n\tat com.test.Main.main(Main.java:27)\n```\n\n如果我们不希望某些属性参与到序列化中进行保存，我们可以添加`transient`关键字：\n\n```java\npublic static void main(String[] args) {\n    try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(\"output.txt\"));\n         ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(\"output.txt\"))){\n        People people = new People(\"lbw\");\n        outputStream.writeObject(people);\n        outputStream.flush();\n        people = (People) inputStream.readObject();\n        System.out.println(people.name);  \u002F\u002F虽然能得到对象，但是name属性并没有保存，因此为null\n    }catch (IOException | ClassNotFoundException e) {\n        e.printStackTrace();\n    }\n}\n\nstatic class People implements Serializable{\n    private static final long serialVersionUID = 1234567;\n\n    transient String name;\n\n    public People(String name){\n        this.name = name;\n    }\n}\n```\n\n其实我们可以看到，在一些JDK内部的源码中，也存在大量的transient关键字，使得某些属性不参与序列化，取消这些不必要保存的属性，可以节省数据空间占用以及减少序列化时间。\n\n***\n\n## Java I\u002FO编程实战\n\n### 图书管理系统\n\n要求实现一个图书管理系统（控制台），支持以下功能：保存书籍信息（要求持久化），查询、添加、删除、修改书籍信息。\n",{"data":466,"status":460,"success":461},[467,472],{"id":8,"image":468,"link":469,"name":470,"type":471},"\u002Fimage\u002Fadv\u002Frainyun-2025-06.webp","https:\u002F\u002Fwww.rainyun.com\u002Fitbaima_","雨云优惠购","cloud",{"id":66,"image":473,"link":474,"name":475,"type":476},"\u002Fimage\u002Fadv\u002Fsimcard-2025-11.webp","https:\u002F\u002Fmall.itbaima.cn","号卡优惠","simcard"]